阅读源码后可以发现,和一般自定义转场动画一致,新建继承 NSObject
子类,遵守 UIViewControllerAnimatedTransitioning
协议。
实现两个代理方法:
返回动画持续时间代理:1 2 3 func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 0.75 }
自定义动画代理:1 2 3 func animateTransition(transitionContext: UIViewControllerContextTransitioning) { // 自定义动画函数 }
参数 transitionContext
可以可以通过 func viewForKey(key: String) -> UIView?
/ public func viewControllerForKey(key: String) -> UIViewController?
取出转场动画的对应 fromView/toView
/ formViewController/toViewController
对应的 key
值:
1 2 3 4 5 6 7 viewForKey: UITransitionContextFromViewKey UITransitionContextToViewKey viewControllerForKey: UITransitionContextFromViewControllerKey UITransitionContextToViewControllerKey
在 Explode
动画中主要在于屏幕快照的获取以及快照的区域分剪,核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 获取 fromView 的快照 let fromViewSnapshot = fromView.snapshotViewAfterScreenUpdates(false) // 将快照剪切成小块加到 containerView 上 for x in 0.0.stride(to: Double(size.width), by: Double(size.width / xFactor)) { for y in 0.0.stride(to: Double(size.height), by: Double(size.height / yFactor)) { let snapshotRegion = CGRect(x: CGFloat(x), y: CGFloat(y), width: size.width / xFactor, height: size.height / yFactor) // 按所给区域获得快照的小块 let snapshot = fromViewSnapshot.resizableSnapshotViewFromRect(snapshotRegion, afterScreenUpdates: false, withCapInsets: UIEdgeInsetsZero) // 主要是设置位置 snapshot.frame = snapshotRegion // 将拼成的 fromView 快照加到 containerView的最顶层 containerView.addSubview(snapshot) snapshots.append(snapshot) } } // 将 fromView 隐藏 containerView.sendSubviewToBack(fromView)
剩下的就是对 每一个小块的动画处理,并在动画结束后调用:
1 ransitionContext.completeTransition(!transitionContext.transitionWasCancelled())
这都很简单,难的是如何结合手势使用,这是最值得学习的地方,理解不深,可以 clone 源码 学习。
实现过程主要是对 UIPercentDrivenInteractiveTransition
的学习使用,和 IBAnimatable
的实现不同,我们采用 NavigationController
管理界面,在 FirstViewController
的 func viewWillAppear(animated: Bool) {}
内设置代理: navigationController?.delegate = self
(如果在方法: func viewDidLoad() {}
设置代理会导致转场取消后无法再次进行自定义动画转场 ) 实现代理方法:
1 2 3 4 5 6 7 8 9 10 extension FirstViewController: UINavigationControllerDelegate { func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { if operation == UINavigationControllerOperation.Push { // ExplodeAnimator 即为自定义的转场动画 return ExplodeAnimator() } else { return nil } } }
之后就是对 SecondViewController
内进行自定义手势 popViewController
: 首先对 view
添加返回手势:
1 2 3 4 5 view.addGestureRecognizer({ let pan = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(SecondViewController.pan(_:))) pan.edges = UIRectEdge.Left return pan }())
手势回调方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func pan(edgePan: UIScreenEdgePanGestureRecognizer) { let progress = edgePan.translationInView(self.view).x / self.view.bounds.width if edgePan.state == UIGestureRecognizerState.Began { self.percentDrivenTransition = UIPercentDrivenInteractiveTransition() self.navigationController?.popViewControllerAnimated(true) } else if edgePan.state == UIGestureRecognizerState.Changed { self.percentDrivenTransition?.updateInteractiveTransition(progress) } else if edgePan.state == UIGestureRecognizerState.Cancelled || edgePan.state == UIGestureRecognizerState.Ended { if progress > 0.5 { self.percentDrivenTransition?.finishInteractiveTransition() } else { self.percentDrivenTransition?.cancelInteractiveTransition() } self.percentDrivenTransition = nil } }
同样,在 SecondViewController
的 func viewWillAppear(animated: Bool) {}
方法内设置代理: navigationController!.delegate = self
,区别只是在于多实现一个代理方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 extension SecondViewController: UINavigationControllerDelegate { func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { if operation == UINavigationControllerOperation.Pop { return ExplodeAnimator() } else { return nil } } func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { if animationController is ExplodeAnimator { return self.percentDrivenTransition } else { return nil } } }
大功告成,
效果展示:
代码地址:
CodeDemo 。
IBAnimatable 源码的实现基于高度的封装,这也是望尘莫及的地方。