分享最热门的ios资讯
授权转载,作者:ZeroJ
前言
其实, Apple已经提供了navigationController中的控制器都有一个从屏幕左边滑动pop的手势, 而且转换控制器之间的各种动画也是已经实现好了, 但是现在很多APP中都有全屏滑动返回的功能, 确实手机屏幕变大后在一定水平上使用是方便了很多。暂且不管这种交互设计好还是不好, 既然这么多的APP(微博, QQ, 简书, 网易面目目样闻...)中都在使用, 肯定在开发中实现这个功能也是须要的了。
最终效果
首先展示一下最终的使用要领, 使用还是对照方便
第一种, 使用提供的自界说的navigationController
如果在storyboard中使用, 子需要将navigationController设置为自界说的即可, 默认拥有全屏滑动返回功能, 如果需要关闭, 在需要的地方设置如下即可
// 设置为true的时候开启全屏滑动返回功能, 设置为false, 关闭 (navigationController as? CustomNavigationController)?.enabledFullScreenPop(isEnabled: false)
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地址。
用意志战胜身体的惰性!
本文由 姬長信 创作,文章地址:https://blog.isoyu.com/archives/573.html
采用知识共享署名4.0 国际许可协议进行许可。除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。最后编辑时间为:7 月 4, 2016 at 03:59 下午