Instantly share code, notes, and snippets.
Created
March 11, 2020 13:24
-
Star
(4)
4
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save yesleon/0e72ca8ff9bd9b684af22093d70bb751 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// InteractivePushableNavigationController.swift | |
// InteractivePushGestureRecognizerExample | |
// | |
// Created by Li-Heng Hsu on 2020/3/11. | |
// Copyright © 2020 Li-Heng Hsu. All rights reserved. | |
// | |
import UIKit | |
class InteractivePushableNavigationController: UINavigationController { | |
// 因為官方沒有統合用來追蹤pop、push事件的API,所以只好自己實作。 | |
enum TransitionState { | |
case popping([UIViewController]), pushing, setting, none | |
} | |
private(set) var transitionState = TransitionState.none | |
// 將被pop掉的VC儲存到一個stack裡。 | |
var poppedViewControllerStack = [UIViewController]() | |
// 負責interactive push轉場的物件。 | |
var interactivePushTransition: InteractivePushTransition? | |
// Interactive push手勢。 | |
lazy var interactivePushGestureRecognizer: UIGestureRecognizer = { | |
let gesture = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(startPushGesture(_:))) | |
gesture.edges = .right | |
return gesture | |
}() | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
self.delegate = self | |
// 設定手勢。 | |
self.view.addGestureRecognizer(interactivePushGestureRecognizer) | |
self.interactivePopGestureRecognizer?.delegate = self | |
self.interactivePushGestureRecognizer.delegate = self | |
} | |
// 負責處理interactive push手勢,轉給self.parallaxTransitionController。 | |
@objc func startPushGesture(_ sender: UIScreenEdgePanGestureRecognizer) { | |
let percentage: CGFloat = (view.frame.width - sender.location(in: view).x) / view.frame.width | |
switch sender.state { | |
case .began: | |
guard let vc = poppedViewControllerStack.last else { return } | |
interactivePushTransition = .init() | |
super.pushViewController(vc, animated: true) | |
case .changed: | |
interactivePushTransition?.update(percentage) | |
case .ended: | |
if sender.velocity(in: view).x > 10 { | |
interactivePushTransition?.cancel() | |
} else if sender.velocity(in: view).x < -10 { | |
interactivePushTransition?.finish() | |
poppedViewControllerStack.removeLast() | |
} else { | |
if percentage >= 0.5 { | |
interactivePushTransition?.finish() | |
poppedViewControllerStack.removeLast() | |
} else { | |
interactivePushTransition?.cancel() | |
} | |
} | |
interactivePushTransition = nil | |
default: | |
break | |
} | |
} | |
// 將pop、push事件儲存成self.transitionState。 | |
override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) { | |
super.setViewControllers(viewControllers, animated: animated) | |
self.transitionState = .setting | |
} | |
override func pushViewController(_ viewController: UIViewController, animated: Bool) { | |
super.pushViewController(viewController, animated: animated) | |
self.transitionState = .pushing | |
} | |
override func popViewController(animated: Bool) -> UIViewController? { | |
guard let poppedVC = super.popViewController(animated: animated) else { return nil } | |
self.transitionState = .popping([poppedVC]) | |
return poppedVC | |
} | |
override func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? { | |
guard let poppedVCs = super.popToViewController(viewController, animated: animated) else { return nil } | |
self.transitionState = .popping(poppedVCs) | |
return poppedVCs | |
} | |
override func popToRootViewController(animated: Bool) -> [UIViewController]? { | |
guard let poppedVCs = super.popToRootViewController(animated: animated) else { return nil } | |
self.transitionState = .popping(poppedVCs) | |
return poppedVCs | |
} | |
} | |
extension InteractivePushableNavigationController: UIGestureRecognizerDelegate { | |
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { | |
switch gestureRecognizer { | |
case interactivePopGestureRecognizer: | |
return viewControllers.count > 1 | |
case interactivePushGestureRecognizer: | |
return !poppedViewControllerStack.isEmpty | |
default: | |
return true | |
} | |
} | |
} | |
extension InteractivePushableNavigationController: UINavigationControllerDelegate { | |
// 只有在interactive push的時候,self.parallaxTransitionController才會存在,才會被調用。其它時候都是回傳nil用內建的轉場。 | |
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { | |
interactivePushTransition | |
} | |
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { | |
interactivePushTransition | |
} | |
// 獲取push與pop事件。 | |
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { | |
switch self.transitionState { | |
case .popping(let poppedVCs): | |
self.poppedViewControllerStack.append(contentsOf: poppedVCs.reversed()) | |
case .pushing, .setting: | |
self.poppedViewControllerStack.removeAll() | |
case .none: | |
break | |
} | |
self.transitionState = .none | |
} | |
} | |
class InteractivePushTransition: UIPercentDrivenInteractiveTransition, UIViewControllerAnimatedTransitioning { | |
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { | |
0.5 | |
} | |
// 模仿內建的parallax效果。 | |
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { | |
guard let fromView = transitionContext.view(forKey: .from), let toView = transitionContext.view(forKey: .to) else { return } | |
let containerView = transitionContext.containerView | |
let dimmingView = UIView() | |
dimmingView.frame = fromView.frame | |
containerView.insertSubview(toView, aboveSubview: fromView) | |
containerView.insertSubview(dimmingView, belowSubview: toView) | |
dimmingView.addSubview(fromView) | |
fromView.frame = dimmingView.bounds | |
toView.frame = dimmingView.frame | |
toView.frame.origin.x += toView.frame.width | |
UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: .curveLinear, animations: { | |
toView.frame = dimmingView.frame | |
dimmingView.alpha = 0.9 | |
dimmingView.frame.origin.x -= dimmingView.frame.width * 0.3 | |
}) { (finished) in | |
dimmingView.removeFromSuperview() | |
if transitionContext.transitionWasCancelled { | |
containerView.addSubview(fromView) | |
transitionContext.completeTransition(false) | |
} else { | |
transitionContext.completeTransition(true) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment