Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save lipka/055300896d7e9fc3cff98288270dfaf9 to your computer and use it in GitHub Desktop.
Save lipka/055300896d7e9fc3cff98288270dfaf9 to your computer and use it in GitHub Desktop.
Action Sheet Presentation Controller

Action Sheet Presentation Controller

Use any view controller in an action sheet. This is not worthy of a dedicated pod or a framework. Please don't make it one.

Usage

Return an ActionSheetPresentationController from presentationController(forPresented:presenting:) and make sure the presented view controller's view has a measurable Auto Layout size.

To-Dos

  • Respect preferredContentSize instead of only Auto Layout size
  • Adapt to regular width by switching to a popover presentation
  • Switch to a normal UIButton instead of a custom dismiss button.
// Created by Caleb Davenport on 7/14/17.
import UIKit
final class ActionSheetPresentationController: UIPresentationController {
// MARK: - Properties
private var dimmingView: UIView!
private var customPresentedView: UIView!
override var presentedView: UIView? {
return customPresentedView
}
override var frameOfPresentedViewInContainerView: CGRect {
let size = customPresentedView.systemLayoutSizeFitting(
containerView!.bounds.size,
withHorizontalFittingPriority: UILayoutPriorityRequired,
verticalFittingPriority: UILayoutPriorityFittingSizeLevel)
let (slice, _) = containerView!.bounds.divided(atDistance: size.height, from: .maxYEdge)
return slice
}
// MARK: - UIPresentationController
override func presentationTransitionWillBegin() {
super.presentationTransitionWillBegin()
if dimmingView == nil {
dimmingView = UIView()
dimmingView.backgroundColor = UIColor(white: 0, alpha: 0.4)
let cancelGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(cancel))
dimmingView.addGestureRecognizer(cancelGestureRecognizer)
dimmingView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
dimmingView.frame = containerView!.bounds
containerView!.addSubview(dimmingView)
}
if customPresentedView == nil {
let dismissButton = ActionSheetPresentationControllerDismissButton()
dismissButton.addTarget(self, action: #selector(cancel), for: .touchUpInside)
dismissButton.setContentHuggingPriority(UILayoutPriorityDefaultHigh, for: .vertical)
let dismissButtonSectionView = ActionSheetPresentationControllerSectionView()
dismissButton.translatesAutoresizingMaskIntoConstraints = false
dismissButtonSectionView.addSubview(dismissButton)
let presentedViewControllerSectionView = ActionSheetPresentationControllerSectionView()
presentedViewController.view.translatesAutoresizingMaskIntoConstraints = false
presentedViewControllerSectionView.addSubview(presentedViewController.view)
NSLayoutConstraint.activate([
dismissButton.leadingAnchor.constraint(equalTo: dismissButtonSectionView.leadingAnchor),
dismissButton.trailingAnchor.constraint(equalTo: dismissButtonSectionView.trailingAnchor),
dismissButton.topAnchor.constraint(equalTo: dismissButtonSectionView.topAnchor),
dismissButton.bottomAnchor.constraint(equalTo: dismissButtonSectionView.bottomAnchor),
presentedViewController.view.leadingAnchor.constraint(equalTo: presentedViewControllerSectionView.leadingAnchor),
presentedViewController.view.trailingAnchor.constraint(equalTo: presentedViewControllerSectionView.trailingAnchor),
presentedViewController.view.topAnchor.constraint(equalTo: presentedViewControllerSectionView.topAnchor),
presentedViewController.view.bottomAnchor.constraint(equalTo: presentedViewControllerSectionView.bottomAnchor)])
let stackView = UIStackView(arrangedSubviews: [presentedViewControllerSectionView, dismissButtonSectionView])
stackView.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
stackView.axis = .vertical
stackView.isLayoutMarginsRelativeArrangement = true
stackView.spacing = 10
stackView.layoutMargins = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
customPresentedView = stackView
}
dimmingView.alpha = 0
presentedViewController.transitionCoordinator?.animate(alongsideTransition: { _ in
self.dimmingView.alpha = 1
})
}
override func dismissalTransitionWillBegin() {
presentedViewController.transitionCoordinator?.animate(alongsideTransition: { _ in
self.dimmingView.alpha = 0
})
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
self.customPresentedView.frame = self.frameOfPresentedViewInContainerView
})
}
// MARK: - Private
@objc private func cancel() {
presentedViewController.dismiss(animated: true, completion: nil)
}
}
// Created by Caleb Davenport on 7/14/17.
import UIKit
final class ActionSheetPresentationControllerDismissButton: UIControl {
// MARK: - Properties
private let textLabel: UILabel = {
let view = UILabel()
view.font = UIFont.systemFont(ofSize: 20, weight: UIFontWeightSemibold)
view.text = L10n.General.done.localizedCapitalized
view.textAlignment = .center
return view
}()
override var intrinsicContentSize: CGSize {
return CGSize(width: UIViewNoIntrinsicMetric, height: 57)
}
// MARK: - Initializers
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
// MARK: - UIView
override func layoutSubviews() {
super.layoutSubviews()
textLabel.frame = bounds
}
override func tintColorDidChange() {
super.tintColorDidChange()
textLabel.textColor = tintColor
}
// MARK: - Private
private func setup() {
textLabel.textColor = tintColor
addSubview(textLabel)
}
}
// Created by Caleb Davenport on 7/14/17.
import UIKit
final class ActionSheetPresentationControllerSectionView: UIView {
// MARK: - Properties
private let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .extraLight))
// MARK: - Initializers
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
override func layoutSubviews() {
super.layoutSubviews()
visualEffectView.frame = bounds
}
// MARK: - Private
private func setup() {
layer.masksToBounds = true
layer.cornerRadius = 14
visualEffectView.isUserInteractionEnabled = false
addSubview(visualEffectView)
}
}
// Created by Caleb Davenport on 7/14/17.
import UIKit
final class DatePickerViewController: UIViewController {
// MARK: - Properties
let datePicker = UIDatePicker()
// MARK: - Initializers
init() {
super.init(nibName: nil, bundle: nil)
modalPresentationStyle = .custom
transitioningDelegate = self
}
@available(*, unavailable)
required init?(coder: NSCoder) {
unimplemented()
}
// MARK: - UIViewController
override func viewDidLoad() {
super.viewDidLoad()
datePicker.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(datePicker)
NSLayoutConstraint.activate([
datePicker.leadingAnchor.constraint(equalTo: view.leadingAnchor),
datePicker.trailingAnchor.constraint(equalTo: view.trailingAnchor),
datePicker.topAnchor.constraint(equalTo: view.topAnchor),
datePicker.bottomAnchor.constraint(equalTo: view.bottomAnchor)])
}
}
extension DatePickerViewController: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return ActionSheetPresentationController(presentedViewController: presented, presenting: presenting)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment