转载

分享iOS中实现navigationController全屏手势滑动pop

授权转载,作者: ZeroJ

前言

其实, Apple已经提供了navigationController中的控制器都有一个从屏幕左边滑动pop的手势, 并且转换控制器之间的各种动画也是已经实现好了, 但是现在很多APP中都有全屏滑动返回的功能, 确实手机屏幕变大后在一定程度上使用是方便了很多。暂且不管这种交互设计好还是不好, 既然这么多的APP(微博, QQ, 简书, 网易新闻...)中都在使用, 肯定在开发中实现这个功能也是必要的了。

最终效果

分享iOS中实现navigationController全屏手势滑动pop

首先展示一下最终的使用方法, 使用还是比较方便

  • 第一种, 使用提供的自定义的navigationController

如果在storyboard中使用, 子需要将navigationController设置为自定义的即可, 默认拥有全屏滑动返回功能, 如果需要关闭, 在需要的地方设置如下即可

// 设置为true的时候开启全屏滑动返回功能, 设置为false, 关闭    (navigationController as? CustomNavigationController)?.enabledFullScreenPop(isEnabled: false)

分享iOS中实现navigationController全屏手势滑动pop

storyboard中使用

如果使用代码初始化, 那么直接使用自定义的navigationController初始化即可

// 同样的默认是开启全屏滑动返回功能的 let navi = CustomNavigationController(rootViewController: rootVc) //如果需要关闭或者重新开启, 在需要的地方使用下面方法 (navigationController as? CustomNavigationController)?.enabledFullScreenPop(isEnabled: false)
  • 第二种, 使用提供的navigationController的分类

这种方法, 并没有默认开启, 需要我们自己开启或者关闭全屏滑动返回功能

// 在需要的地方, 获取到navigationController, 然后使用分类方法开启(关闭)全屏返回手势即可 navigationController?.zj_enableFullScreenPop(isEnabled: true)

实现方法:实现的方法很多, 比如可以利用系统提供的navigationController的手势方法, 利用运行时获取到这个手势的target和selector, 然后, 我们使用分类或者自定义navigationController在上面添加一个pan手势, 将这个手势的target和selector设置为运行时获取到系统手势的target和selector, 那么, 这个手势就拥有了和系统滑动返回相同的效果, 实现上还是很方便的

但是这里, 我想介绍的是另一种Apple推荐的自定义转场动画的方法,

关于自定义转场动画的各种知识, 如果你不是很熟悉, 介意大家看看我之前的 这篇文章 介绍(当时写就是为了实现这篇文章铺垫), 里面介绍了详细的自定义教程, 不过利用是示例了present/dismiss的使用。

  • 新建一个ZJNavigationControllerDelegate用于自定义的navigationController的delegate

class ZJNavigationControllerDelegate: NSObject, UINavigationControllerDelegate {  let animator = ZJNavigationControllerAnimator()  let interactive = ZJNavigationControllerInteractiveTransition()  var panGesture: UIPanGestureRecognizer! = nil {      didSet {          interactive.panGesture = panGesture      }  }  func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {      interactive.navigationController = navigationController      animator.operation = operation      return animator  }  // 这里是手势交互动画需要的对象  func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {      return interactive.isInteracting ? interactive : nil  } //    deinit { //        print("/(self.debugDescription) --- 销毁") //    } }
  • 新建一个ZJNavigationControllerAnimator继承自NSObject,并实现UIViewControllerAnimatedTransitioning协议, 来实现具体的动画

class ZJNavigationControllerAnimator: NSObject, UIViewControllerAnimatedTransitioning {   let duration = 0.35   var operation: UINavigationControllerOperation = .none   func transitionDuration(_ transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {       return duration   }   func animateTransition(_ transitionContext: UIViewControllerContextTransitioning) {       // fromVc 总是获取到正在显示在屏幕上的Controller       let fromVc = transitionContext.viewController(forKey: UITransitionContextFromViewControllerKey)!       // toVc 总是获取到将要显示的controller       let toVc = transitionContext.viewController(forKey: UITransitionContextToViewControllerKey)!       let containView = transitionContext.containerView()       let toView: UIView       let fromView: UIView       // Animators should not directly manipulate a view controller's views and should       // use viewForKey: to get views instead.       if transitionContext.responds(to:NSSelectorFromString("viewForKey:")) {           // 通过这种方法获取到view不一定是对应controller.view           toView = transitionContext.view(forKey: UITransitionContextToViewKey)!           fromView = transitionContext.view(forKey: UITransitionContextFromViewKey)!       } else {           toView = toVc.view           fromView = fromVc.view       }       // 最终显示在屏幕上的controller的frame       let visibleFrame = transitionContext.initialFrame(for: fromVc)       // 隐藏在右边的controller的frame       let rightHiddenFrame = CGRect(origin: CGPoint(x: visibleFrame.width, y: visibleFrame.origin.y) , size: visibleFrame.size)       // 隐藏在左边的controller的frame       let leftHiddenFrame = CGRect(origin: CGPoint(x: -visibleFrame.width/2, y: visibleFrame.origin.y) , size: visibleFrame.size)       if operation == .push {// push           toView.frame = rightHiddenFrame           fromView.frame = visibleFrame           //  添加toview到最上面(fromView是当前显示在屏幕上的view不用添加)           containView.addSubview(toView)       } else {// pop           fromView.frame = visibleFrame           toView.frame = leftHiddenFrame           // 有时需要将toView添加到fromView的下面便于执行动画           containView.insertSubview(toView, belowSubview: fromView)       }       UIView.animate(withDuration: duration, delay: 0.0, options: [.curveLinear], animations: {           if self.operation == .push {               toView.frame = visibleFrame               fromView.frame = leftHiddenFrame           } else {               fromView.frame = rightHiddenFrame               toView.frame = visibleFrame           }       }) { (_) in           let cancelled = transitionContext.transitionWasCancelled()           if cancelled {               // 如果中途取消了就移除toView(可交互的时候会发生)               toView.removeFromSuperview()           }           // 通知系统动画是否完成或者取消了(必须)           transitionContext.completeTransition(!cancelled)       }   } //    deinit { //        print("/(self.debugDescription) --- 销毁") //    } }
  • 新建一个ZJNavigationControllerInteractiveTransition继承自

UIPercentDrivenInteractiveTransition, 来处理手势的过程 class ZJNavigationControllerInteractiveTransition: UIPercentDrivenInteractiveTransition {   var panGesture: UIPanGestureRecognizer! = nil {       didSet {           panGesture.addTarget(self, action: #selector(self.handlePan(gesture:)))       }   }   var containerView: UIView!   var navigationController: UINavigationController! = nil {       didSet {           containerView = navigationController.view           containerView.addGestureRecognizer(panGesture)       }   }   var isInteracting = false   override init() {       super.init()   }   func handlePan(gesture: UIPanGestureRecognizer) {       func finishOrCancel() {           let translation = gesture.translation(in: containerView)           let percent = translation.x / containerView.bounds.width           let velocityX = gesture.velocity(in: containerView).x           let isFinished: Bool           // 修改这里可以改变手势结束时的处理           if velocityX > 100 {               isFinished = true           } else if percent > 0.5 {               isFinished = true           } else {               isFinished = false           }           isFinished ? finish() : cancel()       }       switch gesture.state {       case .began:           isInteracting = true           // pop           if navigationController.viewControllers.count > 0 {               _ = navigationController.popViewController(animated: true)           }       case .changed:           if isInteracting {               let translation = gesture.translation(in: containerView)               var percent = translation.x / containerView.bounds.width               percent = max(percent, 0)               update(percent)           }       case .cancelled:           if isInteracting {               finishOrCancel()               isInteracting = false           }       case .ended:           if isInteracting {               finishOrCancel()               isInteracting = false           }       default:           break       }   } }
  • 最后自定义navigationController

class CustomNavigationController: UINavigationController {   private(set) var panGesture: UIPanGestureRecognizer?   private var customDelegate: CustomNavigationControllerDelegate?   required init?(coder aDecoder: NSCoder) {       super.init(coder: aDecoder)       enabledFullScreenPop(isEnabled: true)   }   override init(rootViewController: UIViewController) {       super.init(rootViewController: rootViewController)       enabledFullScreenPop(isEnabled: true)   }   init() {       super.init(nibName: nil, bundle: nil)       enabledFullScreenPop(isEnabled: true)   }   // 开启或者关闭全屏pop手势(默认开启)   func enabledFullScreenPop(isEnabled: Bool) {       if isEnabled {           if customDelegate == nil {               // 创建代理对象               customDelegate = CustomNavigationControllerDelegate()               // 创建手势               panGesture = UIPanGestureRecognizer()               // 传递手势给代理               customDelegate?.panGesture = panGesture               // 设置代理为自定义的               delegate = customDelegate           }       } else {           customDelegate = nil           panGesture = nil           delegate = nil       }   } }

到这里, 实现的全部过程就完成了, 如果你对代码不是很理解, 建议先去看看自定义转场动画相关的教程, 或者 看看这里 。使用效果如图所示,当然了,这里并没有处理控制器中如果有scrollView的时候的可能的手势冲突, 大家可以自己去尝试处理一下,欢迎关注,欢迎star,同时附上 Demo地址 。

原文  http://www.cocoachina.com/ios/20160704/16914.html
正文到此结束
Loading...