From 7d9f3db9645449e8c43f43ba00da72970c97e103 Mon Sep 17 00:00:00 2001 From: Erfan Andesta Date: Tue, 24 Dec 2019 11:58:00 +0330 Subject: [PATCH 1/3] add dynamic height and postion to popup --- .../BottomPopupDismissAnimator.swift | 9 +++++++-- ...tomPopupDismissInteractionController.swift | 16 ++++++++++----- .../BottomPopupNavigationController.swift | 10 +++++++++- .../BottomPopupPresentAnimator.swift | 8 +++++++- .../BottomPopupPresentationController.swift | 20 ++++++++++++++++--- .../BottomPopupTransitionHandler.swift | 16 +++++++++++---- .../BottomPopupUtils.swift | 6 +++++- .../BottomPopupViewController.swift | 13 +++++++++--- 8 files changed, 78 insertions(+), 20 deletions(-) diff --git a/BottomPopup/BottomPopupController/BottomPopupDismissAnimator.swift b/BottomPopup/BottomPopupController/BottomPopupDismissAnimator.swift index 249411e..2ed71ca 100644 --- a/BottomPopup/BottomPopupController/BottomPopupDismissAnimator.swift +++ b/BottomPopup/BottomPopupController/BottomPopupDismissAnimator.swift @@ -22,8 +22,13 @@ class BottomPopupDismissAnimator: NSObject, UIViewControllerAnimatedTransitionin func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let fromVC = transitionContext.viewController(forKey: .from)! - let dismissFrame = CGRect(origin: CGPoint(x: 0, y: UIScreen.main.bounds.size.height), size: fromVC.view.frame.size) - + let dismissFrame: CGRect! + switch attributesOwner.getPosition() { + case .top: + dismissFrame = CGRect(origin: CGPoint(x: 0, y: -fromVC.view.frame.height), size: fromVC.view.frame.size) + case .bottom: + dismissFrame = CGRect(origin: CGPoint(x: 0, y: UIScreen.main.bounds.size.height), size: fromVC.view.frame.size) + } UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { fromVC.view.frame = dismissFrame }) { (_) in diff --git a/BottomPopup/BottomPopupController/BottomPopupDismissInteractionController.swift b/BottomPopup/BottomPopupController/BottomPopupDismissInteractionController.swift index fbc5d7d..aca50d6 100644 --- a/BottomPopup/BottomPopupController/BottomPopupDismissInteractionController.swift +++ b/BottomPopup/BottomPopupController/BottomPopupDismissInteractionController.swift @@ -15,26 +15,27 @@ protocol BottomPopupDismissInteractionControllerDelegate: class { class BottomPopupDismissInteractionController: UIPercentDrivenInteractiveTransition { private let kMinPercentOfVisiblePartToCompleteAnimation = CGFloat(0.5) - private let kSwipeDownThreshold = CGFloat(1000) + private let kSwipeThreshold = CGFloat(1000) private weak var presentedViewController: BottomPresentableViewController? private weak var transitioningDelegate: BottomPopupTransitionHandler? weak var delegate: BottomPopupDismissInteractionControllerDelegate? - + private var position: PopupPoistion private var currentPercent: CGFloat = 0 { didSet { delegate?.dismissInteractionPercentChanged(from: oldValue, to: currentPercent) } } - init(presentedViewController: BottomPresentableViewController?) { + init(presentedViewController: BottomPresentableViewController?, position: PopupPoistion) { self.presentedViewController = presentedViewController + self.position = position self.transitioningDelegate = presentedViewController?.transitioningDelegate as? BottomPopupTransitionHandler super.init() preparePanGesture(in: presentedViewController?.view) } private func finishAnimation(withVelocity velocity: CGPoint) { - if currentPercent > kMinPercentOfVisiblePartToCompleteAnimation || velocity.y > kSwipeDownThreshold { + if currentPercent > kMinPercentOfVisiblePartToCompleteAnimation || velocity.y > kSwipeThreshold { finish() } else { cancel() @@ -48,7 +49,12 @@ class BottomPopupDismissInteractionController: UIPercentDrivenInteractiveTransit @objc private func handlePanGesture(_ pan: UIPanGestureRecognizer) { let translationY = pan.translation(in: presentedViewController?.view).y - currentPercent = min(max(translationY/(presentedViewController?.view.frame.size.height ?? 0), 0), 1) + switch position { + case .top: + currentPercent = min(max(-translationY/(presentedViewController?.view.frame.size.height ?? 0), 0), 1) + case .bottom: + currentPercent = min(max(translationY/(presentedViewController?.view.frame.size.height ?? 0), 0), 1) + } switch pan.state { case .began: diff --git a/BottomPopup/BottomPopupController/BottomPopupNavigationController.swift b/BottomPopup/BottomPopupController/BottomPopupNavigationController.swift index a066c54..362f1ee 100644 --- a/BottomPopup/BottomPopupController/BottomPopupNavigationController.swift +++ b/BottomPopup/BottomPopupController/BottomPopupNavigationController.swift @@ -62,7 +62,7 @@ open class BottomPopupNavigationController: UINavigationController, BottomPopupA //MARK: Private Methods private func initialize() { - transitionHandler = BottomPopupTransitionHandler(popupViewController: self) + transitionHandler = BottomPopupTransitionHandler(popupViewController: self, position: getPosition()) transitioningDelegate = transitionHandler modalPresentationStyle = .custom } @@ -96,8 +96,16 @@ open class BottomPopupNavigationController: UINavigationController, BottomPopupA open func getPopupDismissDuration() -> Double { return BottomPopupConstants.kDefaultDismissDuration } + open func getPosition() -> PopupPoistion { + .bottom + } open func getDimmingViewAlpha() -> CGFloat { return BottomPopupConstants.kDimmingViewDefaultAlphaValue } } +extension BottomPopupNavigationController { + func setupHeight(to height: CGFloat) { + transitionHandler?.setHeight(to: height) + } +} diff --git a/BottomPopup/BottomPopupController/BottomPopupPresentAnimator.swift b/BottomPopup/BottomPopupController/BottomPopupPresentAnimator.swift index f96fb55..e0db613 100644 --- a/BottomPopup/BottomPopupController/BottomPopupPresentAnimator.swift +++ b/BottomPopup/BottomPopupController/BottomPopupPresentAnimator.swift @@ -24,7 +24,13 @@ class BottomPopupPresentAnimator: NSObject, UIViewControllerAnimatedTransitionin let toVC = transitionContext.viewController(forKey: .to)! transitionContext.containerView.addSubview(toVC.view) let presentFrame = transitionContext.finalFrame(for: toVC) - let initialFrame = CGRect(origin: CGPoint(x: 0, y: UIScreen.main.bounds.size.height), size: presentFrame.size) + var initialFrame: CGRect + switch attributesOwner.getPosition() { + case .top: + initialFrame = CGRect(origin: CGPoint(x: 0, y: -presentFrame.height), size: presentFrame.size) + case .bottom: + initialFrame = CGRect(origin: CGPoint(x: 0, y: UIScreen.main.bounds.height), size: presentFrame.size) + } toVC.view.frame = initialFrame UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { toVC.view.frame = presentFrame diff --git a/BottomPopup/BottomPopupController/BottomPopupPresentationController.swift b/BottomPopup/BottomPopupController/BottomPopupPresentationController.swift index b883897..b3a6e40 100644 --- a/BottomPopup/BottomPopupController/BottomPopupPresentationController.swift +++ b/BottomPopup/BottomPopupController/BottomPopupPresentationController.swift @@ -11,12 +11,18 @@ import UIKit class BottomPopupPresentationController: UIPresentationController { fileprivate var dimmingView: UIView! - fileprivate let popupHeight: CGFloat + fileprivate var popupHeight: CGFloat + fileprivate let position: PopupPoistion fileprivate let dimmingViewAlpha: CGFloat override var frameOfPresentedViewInContainerView: CGRect { get { - return CGRect(origin: CGPoint(x: 0, y: UIScreen.main.bounds.size.height - popupHeight), size: CGSize(width: presentedViewController.view.frame.size.width, height: popupHeight)) + switch position { + case .top: + return CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: presentedViewController.view.frame.size.width, height: popupHeight)) + case .bottom: + return CGRect(origin: CGPoint(x: 0, y: UIScreen.main.bounds.height - popupHeight), size: CGSize(width: presentedViewController.view.frame.size.width, height: popupHeight)) + } } } @@ -31,9 +37,10 @@ class BottomPopupPresentationController: UIPresentationController { }) } - init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, usingHeight height: CGFloat, andDimmingViewAlpha dimmingAlpha: CGFloat) { + init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, usingHeight height: CGFloat, andDimmingViewAlpha dimmingAlpha: CGFloat, position: PopupPoistion) { self.popupHeight = height self.dimmingViewAlpha = dimmingAlpha + self.position = position super.init(presentedViewController: presentedViewController, presenting: presentingViewController) setupDimmingView() } @@ -67,3 +74,10 @@ private extension BottomPopupPresentationController { dimmingView.addGestureRecognizer(tapGesture) } } +//MARK: - dynamic height based on autolayout - +extension BottomPopupPresentationController { + func setHeight(to height: CGFloat) { + self.popupHeight = height + containerViewWillLayoutSubviews() + } +} diff --git a/BottomPopup/BottomPopupController/BottomPopupTransitionHandler.swift b/BottomPopup/BottomPopupController/BottomPopupTransitionHandler.swift index 88aa888..acc1fe6 100644 --- a/BottomPopup/BottomPopupController/BottomPopupTransitionHandler.swift +++ b/BottomPopup/BottomPopupController/BottomPopupTransitionHandler.swift @@ -15,12 +15,14 @@ class BottomPopupTransitionHandler: NSObject, UIViewControllerTransitioningDeleg private var interactionController: BottomPopupDismissInteractionController? private unowned var popupViewController: BottomPresentableViewController fileprivate weak var popupDelegate: BottomPopupDelegate? + private let position: PopupPoistion var isInteractiveDismissStarted = false + var bottomPopupPresentationController: BottomPopupPresentationController? - init(popupViewController: BottomPresentableViewController) { + init(popupViewController: BottomPresentableViewController, position: PopupPoistion) { self.popupViewController = popupViewController - + self.position = position presentAnimator = BottomPopupPresentAnimator(attributesOwner: popupViewController) dismissAnimator = BottomPopupDismissAnimator(attributesOwner: popupViewController) } @@ -29,14 +31,15 @@ class BottomPopupTransitionHandler: NSObject, UIViewControllerTransitioningDeleg func notifyViewLoaded(withPopupDelegate delegate: BottomPopupDelegate?) { self.popupDelegate = delegate if popupViewController.shouldPopupDismissInteractivelty() { - interactionController = BottomPopupDismissInteractionController(presentedViewController: popupViewController) + interactionController = BottomPopupDismissInteractionController(presentedViewController: popupViewController, position: position) interactionController?.delegate = self } } //MARK: Specific animators func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { - return BottomPopupPresentationController(presentedViewController: presented, presenting: presenting, usingHeight: popupViewController.getPopupHeight(), andDimmingViewAlpha: popupViewController.getDimmingViewAlpha()) + bottomPopupPresentationController = BottomPopupPresentationController(presentedViewController: presented, presenting: presenting, usingHeight: popupViewController.getPopupHeight(), andDimmingViewAlpha: popupViewController.getDimmingViewAlpha(), position: position) + return bottomPopupPresentationController } func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { @@ -57,3 +60,8 @@ extension BottomPopupTransitionHandler: BottomPopupDismissInteractionControllerD popupDelegate?.bottomPopupDismissInteractionPercentChanged(from: oldValue, to: newValue) } } +extension BottomPopupTransitionHandler { + func setHeight(to height: CGFloat) { + bottomPopupPresentationController?.setHeight(to: height) + } +} diff --git a/BottomPopup/BottomPopupController/BottomPopupUtils.swift b/BottomPopup/BottomPopupController/BottomPopupUtils.swift index 8ec24ed..bca519e 100644 --- a/BottomPopup/BottomPopupController/BottomPopupUtils.swift +++ b/BottomPopup/BottomPopupController/BottomPopupUtils.swift @@ -9,7 +9,10 @@ import UIKit typealias BottomPresentableViewController = BottomPopupAttributesDelegate & UIViewController - +public enum PopupPoistion { + case top + case bottom +} public protocol BottomPopupDelegate: class { func bottomPopupViewLoaded() func bottomPopupWillAppear() @@ -35,6 +38,7 @@ public protocol BottomPopupAttributesDelegate: class { func getPopupDismissDuration() -> Double func shouldPopupDismissInteractivelty() -> Bool func getDimmingViewAlpha() -> CGFloat + func getPosition() -> PopupPoistion } public struct BottomPopupConstants { diff --git a/BottomPopup/BottomPopupController/BottomPopupViewController.swift b/BottomPopup/BottomPopupController/BottomPopupViewController.swift index 8fad1e0..dac0093 100644 --- a/BottomPopup/BottomPopupController/BottomPopupViewController.swift +++ b/BottomPopup/BottomPopupController/BottomPopupViewController.swift @@ -62,13 +62,13 @@ open class BottomPopupViewController: UIViewController, BottomPopupAttributesDel //MARK: Private Methods private func initialize() { - transitionHandler = BottomPopupTransitionHandler(popupViewController: self) + transitionHandler = BottomPopupTransitionHandler(popupViewController: self, position: getPosition()) transitioningDelegate = transitionHandler modalPresentationStyle = .custom } private func curveTopCorners() { - let path = UIBezierPath(roundedRect: self.view.bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: getPopupTopCornerRadius(), height: 0)) + let path = UIBezierPath(roundedRect: self.view.bounds, byRoundingCorners: [.bottomRight, .bottomLeft], cornerRadii: CGSize(width: 0, height: getPopupTopCornerRadius())) let maskLayer = CAShapeLayer() maskLayer.frame = self.view.bounds maskLayer.path = path.cgPath @@ -92,7 +92,9 @@ open class BottomPopupViewController: UIViewController, BottomPopupAttributesDel open func getPopupPresentDuration() -> Double { return BottomPopupConstants.kDefaultPresentDuration } - + open func getPosition() -> PopupPoistion { + .bottom + } open func getPopupDismissDuration() -> Double { return BottomPopupConstants.kDefaultDismissDuration } @@ -101,3 +103,8 @@ open class BottomPopupViewController: UIViewController, BottomPopupAttributesDel return BottomPopupConstants.kDimmingViewDefaultAlphaValue } } +extension BottomPopupViewController { + func setupHeight(to height: CGFloat) { + transitionHandler?.setHeight(to: height) + } +} From fb082c304b4affc4fcecfc25f33ad4940e70ba7d Mon Sep 17 00:00:00 2001 From: andestaerfan Date: Wed, 25 Dec 2019 13:06:55 +0330 Subject: [PATCH 2/3] fix bugs --- BottomPopup/Base.lproj/Main.storyboard | 73 +++++++++++-------- .../BottomPopupViewController.swift | 6 +- BottomPopup/ExamplePopupViewController.swift | 8 ++ 3 files changed, 57 insertions(+), 30 deletions(-) diff --git a/BottomPopup/Base.lproj/Main.storyboard b/BottomPopup/Base.lproj/Main.storyboard index 6060c6d..bb9baad 100644 --- a/BottomPopup/Base.lproj/Main.storyboard +++ b/BottomPopup/Base.lproj/Main.storyboard @@ -1,12 +1,11 @@ - - - - + + - + + @@ -33,7 +32,7 @@ - + @@ -158,6 +157,12 @@ + @@ -168,6 +173,9 @@ + + + @@ -198,32 +206,39 @@ - - + + + + + + + - - - - + + + + + + @@ -238,7 +253,7 @@