1 // Copyright (c) 2018, Applidium. All rights reserved 2 // OverlayContainerSheetPresentationController.swift 3 // OverlayContainer 4 // 5 // Created by Gaétan Zanella on 08/04/2020. 6 // 7 8 import UIKit 9 10 /// An `OverlayContainerPresentationController` subclass that can be used to manage the transition animations and the presentation of overlay containers onscreen. 11 /// 12 /// It adds a dimming layer over the presenting content and changes its appearance based on the current container translations. 13 /// It also includes two dismissal gestures: tap-to-dismiss and drag-to-dismiss. 14 /// 15 /// You can subclass this class if you need an extra level of customization. 16 open class OverlayContainerSheetPresentationController: OverlayContainerPresentationController { 17 18 // MARK: - Public properties 19 20 /// The delegate of the presentation controller. 21 open weak var sheetDelegate: OverlayContainerSheetPresentationControllerDelegate? 22 23 /// A dimming view added in the container view. 24 /// 25 /// The default implementation of this property returns the view you provided in the `init` method. 26 open private(set) var dimmingView: OverlayContainerSheetDimmingView? 27 28 /// The underlying gesture recognizer for tap-to-dismiss gesture. 29 open private(set) lazy var dismissingTapGestureRecognizer: UITapGestureRecognizer = self.makeTapGestureRecognizer() 30 31 // MARK: - Private properties 32 33 private lazy var tapGestureRecognizerView: UIView = self.makeTapGestureRecognizerView() 34 35 // MARK: - Life Cycle 36 37 /// Initializes and returns a presentation controller for transitioning between the specified view controllers. 38 /// 39 /// - parameter dimmingView: The view used as a dimming view. The default value is a `TransparentOverlayContainerPresentationDimmingView` instance. 40 /// - parameter presentedViewController: The view controller being presented modally. 41 /// - parameter dimmingView: The view controller whose content represents the starting point of the transition. 42 /// 43 /// - returns: The new `OverlayContainerPresentationController` instance. 44 /// 45 /// The `presentedViewController` can be an overlay container or the parent view controller of an overlay container. 46 public init(dimmingView: OverlayContainerSheetDimmingView? = TransparentOverlayContainerSheetDimmingView(), 47 presentedViewController: UIViewController, 48 presenting: UIViewController?) { 49 self.dimmingView = dimmingView 50 super.init( 51 presentedViewController: presentedViewController, 52 presenting: presenting 53 ) 54 } 55 56 // MARK: - OverlayContainerPresentationController 57 58 open override func overlayContainerViewController(_ containerViewController: OverlayContainerViewController, 59 willTranslateOverlay overlayViewController: UIViewController, 60 transitionCoordinator: OverlayContainerTransitionCoordinator) { 61 let dismissalContext = ConcreteOverlayContainerDismissalPolicyContext( 62 context: transitionCoordinator 63 ) 64 transitionCoordinator.animate(alongsideTransition: { [weak self] context in 65 self?.dimmingView?.overlayViewControllerWillTranslate(context: context) 66 }, completion: nil) 67 let policy = makeDismissalPolicy() 68 if !presentedViewController.isBeingDismissed && policy.shouldDismiss(using: dismissalContext) { 69 presentingViewController.dismiss(animated: true, completion: nil) 70 } 71 } 72 73 // MARK: - UIPresentationController 74 presentationTransitionWillBeginnull75 open override func presentationTransitionWillBegin() { 76 super.presentationTransitionWillBegin() 77 startDimmingViewPresentationTransition() 78 setUpTapGesture() 79 } 80 dismissalTransitionWillBeginnull81 open override func dismissalTransitionWillBegin() { 82 super.dismissalTransitionWillBegin() 83 startDimmingViewDismissalTransition() 84 } 85 presentationTransitionDidEndnull86 open override func presentationTransitionDidEnd(_ completed: Bool) { 87 super.presentationTransitionDidEnd(completed) 88 guard !completed else { return } 89 dimmingView?.removeFromSuperview() 90 tapGestureRecognizerView.removeFromSuperview() 91 } 92 93 // MARK: - Action 94 tapGestureActionnull95 @objc private func tapGestureAction(_ sender: UITapGestureRecognizer) { 96 let shouldDismiss = sheetDelegate?.overlayContainerSheetPresentationControllerShouldDismissOnTap(self) ?? true 97 guard shouldDismiss else { return } 98 presentedViewController.dismiss(animated: true, completion: nil) 99 } 100 101 // MARK: - Private 102 setUpTapGesturenull103 private func setUpTapGesture() { 104 guard tapGestureRecognizerView.superview == nil, 105 dismissingTapGestureRecognizer.isEnabled else { 106 return 107 } 108 containerView?.addSubview(tapGestureRecognizerView) 109 tapGestureRecognizerView.pinToSuperview() 110 tapGestureRecognizerView.addGestureRecognizer(dismissingTapGestureRecognizer) 111 } 112 startDimmingViewPresentationTransitionnull113 private func startDimmingViewPresentationTransition() { 114 guard let dimmingView = dimmingView else { return } 115 if dimmingView.superview == nil { 116 containerView?.addSubview(dimmingView) 117 dimmingView.pinToSuperview() 118 } 119 containerView?.layoutIfNeeded() 120 dimmingView.presentationTransitionWillBegin() 121 presentedViewController.transitionCoordinator?.animate(alongsideTransition: { _ in 122 dimmingView.presentationTransitionDidBegin() 123 }, completion: nil) 124 } 125 startDimmingViewDismissalTransitionnull126 private func startDimmingViewDismissalTransition() { 127 guard let dimmingView = dimmingView else { return } 128 dimmingView.dismissalTransitionWillBegin() 129 presentedViewController.transitionCoordinator?.animate(alongsideTransition: { _ in 130 dimmingView.dismissalTransitionDidBegin() 131 }, completion: nil) 132 } 133 makeDismissalPolicynull134 private func makeDismissalPolicy() -> OverlayContainerSheetDismissalPolicy { 135 sheetDelegate?.overlayContainerSheetDismissalPolicy(for: self) ?? ThresholdOverlayContainerSheetDismissalPolicy() 136 } 137 makeTapGestureRecognizerViewnull138 private func makeTapGestureRecognizerView() -> UIView { 139 UIView() 140 } 141 makeTapGestureRecognizernull142 private func makeTapGestureRecognizer() -> UITapGestureRecognizer { 143 UITapGestureRecognizer(target: self, action: #selector(tapGestureAction(_:))) 144 } 145 } 146 147 public extension OverlayContainerSheetPresentationControllerDelegate { 148 overlayContainerSheetPresentationControllerShouldDismissOnTapnull149 func overlayContainerSheetPresentationControllerShouldDismissOnTap(_ presentationController: OverlayContainerSheetPresentationController) -> Bool { 150 true 151 } 152 overlayContainerSheetDismissalPolicynull153 func overlayContainerSheetDismissalPolicy(for presentationController: OverlayContainerSheetPresentationController) -> OverlayContainerSheetDismissalPolicy { 154 ThresholdOverlayContainerSheetDismissalPolicy() 155 } 156 } 157