diff --git a/PanModal/Controller/PanModalPresentationController.swift b/PanModal/Controller/PanModalPresentationController.swift index 23ae3fd7..525c6ad4 100644 --- a/PanModal/Controller/PanModalPresentationController.swift +++ b/PanModal/Controller/PanModalPresentationController.swift @@ -24,7 +24,6 @@ import UIKit the presented view can define its layout configuration & presentation. */ open class PanModalPresentationController: UIPresentationController { - /** Enum representing the possible presentation states */ @@ -36,7 +35,7 @@ open class PanModalPresentationController: UIPresentationController { /** Constants */ - struct Constants { + enum Constants { static let indicatorYOffset = CGFloat(8.0) static let snapMovementSensitivity = CGFloat(0.7) static let dragIndicatorSize = CGSize(width: 36.0, height: 5.0) @@ -142,7 +141,7 @@ open class PanModalPresentationController: UIPresentationController { /** Override presented view to return the pan container wrapper */ - public override var presentedView: UIView { + override public var presentedView: UIView { return panContainerView } @@ -152,20 +151,20 @@ open class PanModalPresentationController: UIPresentationController { Gesture recognizer to detect & track pan gestures */ private lazy var panGestureRecognizer: UIPanGestureRecognizer = { - let gesture = UIPanGestureRecognizer(target: self, action: #selector(didPanOnPresentedView(_ :))) + let gesture = UIPanGestureRecognizer(target: self, action: #selector(didPanOnPresentedView(_:))) gesture.minimumNumberOfTouches = 1 gesture.maximumNumberOfTouches = 1 gesture.delegate = self return gesture }() - + private lazy var swipeDownGestrue: UISwipeGestureRecognizer = { let gesture = UISwipeGestureRecognizer(target: self, action: #selector(swipeDownAction)) gesture.direction = .down return gesture }() - - @objc func swipeDownAction(){ + + @objc func swipeDownAction() { presentedViewController.dismiss(animated: true) } @@ -183,9 +182,12 @@ open class PanModalPresentationController: UIPresentationController { } override public func presentationTransitionWillBegin() { - guard let containerView = containerView - else { return } + else { return } + + if panContainerView.frame == .zero { + adjustPresentedViewFrame() + } layoutBackgroundView(in: containerView) layoutPresentedView(in: containerView) @@ -229,7 +231,7 @@ open class PanModalPresentationController: UIPresentationController { override public func dismissalTransitionDidEnd(_ completed: Bool) { if !completed { return } - + presentable?.panModalDidDismiss() } @@ -243,7 +245,7 @@ open class PanModalPresentationController: UIPresentationController { guard let self = self, let presentable = self.presentable - else { return } + else { return } self.adjustPresentedViewFrame() if presentable.shouldRoundTopCorners { @@ -251,21 +253,18 @@ open class PanModalPresentationController: UIPresentationController { } }) } - } // MARK: - Public Methods public extension PanModalPresentationController { - /** Transition the PanModalPresentationController to the given presentation state */ func transition(to state: PresentationState) { - guard presentable?.shouldTransition(to: state) == true - else { return } + else { return } presentable?.willTransition(to: state) @@ -286,9 +285,8 @@ public extension PanModalPresentationController { with scroll observation temporarily disabled. */ func performUpdates(_ updates: () -> Void) { - guard let scrollView = presentable?.panScrollable - else { return } + else { return } // Pause scroll observer scrollObserver?.invalidate() @@ -315,20 +313,19 @@ public extension PanModalPresentationController { observe(scrollView: presentable?.panScrollable) configureScrollViewInsets() } - } // MARK: - Presented View Layout Configuration private extension PanModalPresentationController { - /** Boolean flag to determine if the presented view is anchored */ var isPresentedViewAnchored: Bool { - if !isPresentedViewAnimating - && extendsPanScrolling - && presentedView.frame.minY.rounded() <= anchoredYPosition.rounded() { + if !isPresentedViewAnimating, + extendsPanScrolling, + presentedView.frame.minY.rounded() <= anchoredYPosition.rounded() + { return true } @@ -341,13 +338,12 @@ private extension PanModalPresentationController { based on the pan modal presentable. */ func layoutPresentedView(in containerView: UIView) { - /** If the presented view controller does not conform to pan modal presentable don't configure */ guard let presentable = presentable - else { return } + else { return } /** ⚠️ If this class is NOT used in conjunction with the PanModalPresentationAnimator @@ -355,15 +351,15 @@ private extension PanModalPresentationController { in the presentation animator instead of here */ containerView.addSubview(presentedView) - + if presentable.noNeedGes == false { if presentable.canDrag { containerView.addGestureRecognizer(panGestureRecognizer) - }else{ + } else { containerView.addGestureRecognizer(swipeDownGestrue) } } - + if presentable.showDragIndicator { addDragIndicatorView(to: presentedView) } @@ -375,21 +371,18 @@ private extension PanModalPresentationController { setNeedsLayoutUpdate() adjustPanContainerBackgroundColor() } - - /** Reduce height of presentedView so that it sits at the bottom of the screen */ func adjustPresentedViewFrame() { - guard let frame = containerView?.frame - else { return } + else { return } let adjustedSize = CGSize(width: frame.size.width, height: frame.size.height - anchoredYPosition) let panFrame = panContainerView.frame panContainerView.frame.size = frame.size - + if ![shortFormYPosition, longFormYPosition].contains(panFrame.origin.y) { // if the container is already in the correct position, no need to adjust positioning // (rotations & size changes cause positioning to be out of sync) @@ -440,9 +433,8 @@ private extension PanModalPresentationController { Calculates & stores the layout anchor points & options */ func configureViewLayout() { - guard let layoutPresentable = presentedViewController as? PanModalPresentable.LayoutType - else { return } + else { return } shortFormYPosition = layoutPresentable.shortFormYPos longFormYPosition = layoutPresentable.longFormYPos @@ -456,11 +448,10 @@ private extension PanModalPresentationController { Configures the scroll view insets */ func configureScrollViewInsets() { - guard let scrollView = presentable?.panScrollable, !scrollView.isScrolling - else { return } + else { return } /** Disable vertical scroll indicator until we start to scroll @@ -483,24 +474,21 @@ private extension PanModalPresentationController { scrollView.contentInsetAdjustmentBehavior = .never } } - } // MARK: - Pan Gesture Event Handler private extension PanModalPresentationController { - /** The designated function for handling pan gesture events */ @objc func didPanOnPresentedView(_ recognizer: UIPanGestureRecognizer) { - guard shouldRespond(to: recognizer), let containerView = containerView - else { - recognizer.setTranslation(.zero, in: recognizer.view) - return + else { + recognizer.setTranslation(.zero, in: recognizer.view) + return } switch recognizer.state { @@ -514,7 +502,7 @@ private extension PanModalPresentationController { /** If presentedView is translated above the longForm threshold, treat as transition */ - if presentedView.frame.origin.y == anchoredYPosition && extendsPanScrolling { + if presentedView.frame.origin.y == anchoredYPosition, extendsPanScrolling { presentable?.willTransition(to: .longForm) } @@ -526,7 +514,6 @@ private extension PanModalPresentationController { let velocity = recognizer.velocity(in: presentedView) if isVelocityWithinSensitivityRange(velocity.y) { - /** If velocity is within the sensitivity range, transition to a presentation state or dismiss entirely. @@ -538,7 +525,8 @@ private extension PanModalPresentationController { transition(to: .longForm) } else if (nearest(to: presentedView.frame.minY, inValues: [longFormYPosition, containerView.bounds.height]) == longFormYPosition - && presentedView.frame.minY < shortFormYPosition) || presentable?.allowsDragToDismiss == false { + && presentedView.frame.minY < shortFormYPosition) || presentable?.allowsDragToDismiss == false + { transition(to: .shortForm) } else { @@ -546,7 +534,6 @@ private extension PanModalPresentationController { } } else { - /** The `containerView.bounds.height` is used to determine how close the presented view is to the bottom of the screen @@ -577,11 +564,11 @@ private extension PanModalPresentationController { func shouldRespond(to panGestureRecognizer: UIPanGestureRecognizer) -> Bool { guard presentable?.shouldRespond(to: panGestureRecognizer) == true || - !(panGestureRecognizer.state == .began || panGestureRecognizer.state == .cancelled) - else { - panGestureRecognizer.isEnabled = false - panGestureRecognizer.isEnabled = true - return false + !(panGestureRecognizer.state == .began || panGestureRecognizer.state == .cancelled) + else { + panGestureRecognizer.isEnabled = false + panGestureRecognizer.isEnabled = true + return false } return !shouldFail(panGestureRecognizer: panGestureRecognizer) } @@ -616,7 +603,6 @@ private extension PanModalPresentationController { from one view to another in the same pan gesture so don't cancel */ func shouldFail(panGestureRecognizer: UIPanGestureRecognizer) -> Bool { - /** Allow api consumers to override the internal conditions & decide if the pan gesture recognizer should be prioritized. @@ -634,8 +620,8 @@ private extension PanModalPresentationController { isPresentedViewAnchored, let scrollView = presentable?.panScrollable, scrollView.contentOffset.y > 0 - else { - return false + else { + return false } let loc = panGestureRecognizer.location(in: presentedView) @@ -672,7 +658,7 @@ private extension PanModalPresentationController { */ func adjust(toYPosition yPos: CGFloat) { presentedView.frame.origin.y = max(yPos, anchoredYPosition) - + guard presentedView.frame.origin.y > shortFormYPosition else { backgroundView.dimState = .max return @@ -696,7 +682,7 @@ private extension PanModalPresentationController { */ func nearest(to number: CGFloat, inValues values: [CGFloat]) -> CGFloat { guard let nearestVal = values.min(by: { abs(number - $0) < abs(number - $1) }) - else { return number } + else { return number } return nearestVal } } @@ -704,7 +690,6 @@ private extension PanModalPresentationController { // MARK: - UIScrollView Observer private extension PanModalPresentationController { - /** Creates & stores an observer on the given scroll view's content offset. This allows us to track scrolling without overriding the scrollView delegate @@ -717,7 +702,7 @@ private extension PanModalPresentationController { Incase we have a situation where we have two containerViews in the same presentation */ guard self?.containerView != nil - else { return } + else { return } self?.didPanOnScrollView(scrollView, change: change) } @@ -733,21 +718,18 @@ private extension PanModalPresentationController { which allows us to seamlessly transition scrolling from the panContainerView to the scrollView */ func didPanOnScrollView(_ scrollView: UIScrollView, change: NSKeyValueObservedChange) { - guard !presentedViewController.isBeingDismissed, !presentedViewController.isBeingPresented - else { return } + else { return } if !isPresentedViewAnchored && scrollView.contentOffset.y > 0 { - /** Hold the scrollView in place if we're actively scrolling and not handling top bounce */ haltScrolling(scrollView) } else if scrollView.isScrolling || isPresentedViewAnimating { - if isPresentedViewAnchored { /** While we're scrolling upwards on the scrollView, @@ -761,9 +743,9 @@ private extension PanModalPresentationController { haltScrolling(scrollView) } - } else if presentedViewController.view.isKind(of: UIScrollView.self) - && !isPresentedViewAnimating && scrollView.contentOffset.y <= 0 { - + } else if presentedViewController.view.isKind(of: UIScrollView.self), + !isPresentedViewAnimating, scrollView.contentOffset.y <= 0 + { /** In the case where we drag down quickly on the scroll view and let go, `handleScrollViewTopBounce` adds a nice elegant touch. @@ -802,9 +784,8 @@ private extension PanModalPresentationController { So, for example, a UITableViewController. */ func handleScrollViewTopBounce(scrollView: UIScrollView, change: NSKeyValueObservedChange) { - guard let oldYValue = change.oldValue?.y, scrollView.isDecelerating - else { return } + else { return } let yOffset = scrollView.contentOffset.y let presentedSize = containerView?.frame.size ?? .zero @@ -834,7 +815,6 @@ private extension PanModalPresentationController { // MARK: - UIGestureRecognizerDelegate extension PanModalPresentationController: UIGestureRecognizerDelegate { - /** Do not require any other gesture recognizers to fail */ @@ -854,7 +834,6 @@ extension PanModalPresentationController: UIGestureRecognizerDelegate { // MARK: - UIBezierPath private extension PanModalPresentationController { - /** Draws top rounded corners on a given view We have to set a custom path for corner rounding @@ -868,7 +847,7 @@ private extension PanModalPresentationController { // Draw around the drag indicator view, if displayed if presentable?.showDragIndicator == true { - let indicatorLeftEdgeXPos = view.bounds.width/2.0 - Constants.dragIndicatorSize.width/2.0 + let indicatorLeftEdgeXPos = view.bounds.width / 2.0 - Constants.dragIndicatorSize.width / 2.0 drawAroundDragIndicator(currentPath: path, indicatorLeftEdgeXPos: indicatorLeftEdgeXPos) } @@ -886,7 +865,6 @@ private extension PanModalPresentationController { Draws a path around the drag indicator view */ func drawAroundDragIndicator(currentPath path: UIBezierPath, indicatorLeftEdgeXPos: CGFloat) { - let totalIndicatorOffset = Constants.indicatorYOffset + Constants.dragIndicatorSize.height // Draw around drag indicator starting from the left @@ -900,7 +878,6 @@ private extension PanModalPresentationController { // MARK: - Helper Extensions private extension UIScrollView { - /** A flag to determine if a scroll view is scrolling */