|
// 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) |
|
} |
|
} |
Thank you so much, this has been super helpful to me as I was having the same problem.
Two questions though.
When comparing with UIAlertController, I noticed that the sheet's width stays the same in portrait and landscape, and seems to be equal to the layout margin frame in portrait orientation. In order to compute this value in frameOfPresentedViewInContainerView, is there a way I can get the layout margin frame of the portrait orientation even when the phone is actually in landscape?
Originally I was trying to use auto layout constraints to size and place the presented view in the container, but never managed to get it work. Is that the actual recommended way to use frame calculation in frameOfPresentedViewInContainerView instead?
Thanks a lot!