源码

首页 » 归档 » 源码 » 分享iOS中实现navigationController全屏手势滑动pop-ios学习从入门到精通尽在姬长信

分享iOS中实现navigationController全屏手势滑动pop-ios学习从入门到精通尽在姬长信

分享最热门的ios资讯

授权转载,作者:ZeroJ

前言

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

最终效果

1467604940225077.gif

首先展示一下最终的使用要领, 使用还是对照方便

  • 第一种, 使用提供的自界说的navigationController

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

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

1271831-dc06600f84a02c16.jpg

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地址

用意志战胜身体的惰性!

(0)

本文由 姬長信 创作,文章地址:https://blog.isoyu.com/archives/573.html
采用知识共享署名4.0 国际许可协议进行许可。除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。最后编辑时间为:7 月 4, 2016 at 03:59 下午

关键词:

热评文章

发表回复

[必填]

我是人?

提交后请等待三秒以免造成未提交成功和重复