想要在 push
和 pop
viewController
时使用自定义的转场动效,需要设置self.naviagtionController.delegate
, 并实现UINavigationControllerDelegate
的一个方法:
// 返回一个实现了转场动画协议的对象
func navigationController(_ navigationController: UINavigationController,animationControllerFor operation: UINavigationController.Operation,from fromVC: UIViewController,to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {if operation == .push {return a push animator // 实现push动画的对象} if operation == .pop {return a pop animator // 实现pop动画的对象}
}
想要在 present
和 dismiss
viewController
时使用自定义的转场动效,需要设置toViewController.transitioningDelegate
, 并实现UIViewControllerTransitioningDelegate
协议的两个方法:
// 返回一个实现了 present 转场动画协议的对象
func animationController(forPresented presented: UIViewController,presenting: UIViewController,source: UIViewController) -> UIViewControllerAnimatedTransitioning? {return a present animator // 实现 present 动画的对象
}
// 返回一个实现了 dismiss 转场动画协议的对象
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {return a pop animator // 实现 dismiss 动画的对象
}
Tips: 这个协议谁实现都可以:fromVC
or toVC
or new an object
, as you like.
以上2个协议返回的4个animator
都是实现了UIViewControllerAnimatedTransitioning
协议的对象。举例实现如下:
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {return 0.5 // 返回动画时长
}
执行动画的方法animateTransition
,带了一个遵循UIViewControllerContextTransitioning
协议的transitionContext
参数。具体可以取到哪些数据详情可见UIViewControllerContextTransitioning。
以下列举一些常用的:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {// 1. get data for animation (获取动画需要的数据)// animation contianer (动画容器)let containerView = transitionContext.containerView// come from viewController (来源页面的)// viewControllerlet fromVC = transitionContext.viewController(forKey: .from)// viewlet fromView = transitionContext.view(forKey: .from)// 初始framelet fromViewInitialFrame = transitionContext.initialFrame(for: fromVC)// 最终framelet fromViewFinalFrame = transitionContext.finalFrame(for: fromVC)// to viewController (跳转页面的)// viewControllerlet toVC = transitionContext.viewController(forKey: .to)// viewlet toView = transitionContext.view(forKey: .to)// 初始framevar toViewInitialFrame = transitionContext.initialFrame(for: toVC)// 最终framelet toViewFinalFrame = transitionContext.finalFrame(for: toVC)// and so on ...... // do animation with available data (根据拿到的数据做动画)// 2. calculate the value what you want (计算初始位置+最终位置)toViewInitialFrame.origin.x = containerFrame.size.width;toViewInitialFrame.origin.y = containerFrame.size.height;// 3. Add do toView to the contenerView, and set the initial value (添加 toView 到 contianerView 上, 并设置初始值)containerView.addSubview(toView)toView.frame = toViewInitialFrame;// Add additional views required for animation and set initial values// 添加动画所需的其他视图并设置初始值...... // 4. execute animation 执行动画UIView.animate(withDuration: self.transitionDuration(using: transitionContext)) {// 5.1 set final frame for animation viewtoView.frame = toViewFinalFrame// Set additional views final values......} completion: { finish in// 5.2 get animation resultlet success = !transitionContext.transitionWasCancelled// 5.2.1 remove the view if animation failif !success {toView.removeFromSuperview()}// 5.2.1 callback animation resulttransitionContext.completeTransition(success)}
}
动画结束方法:
func animationEnded(_ transitionCompleted: Bool) {// transitionCompleted 动画执行结果: YES-success NO-fail
}
大致跟显示动画一致,转场动画都是需要显示toView
, 让fromView
消失
toView
加到containerView
上,并对齐进行动画。toView
就显示在conatinerView
上,进入的是下一个页面toView
从容器上移除,即还停留在原来的页面上。toView
加到containerView
上,但用的是fromView
进行动画。fromView
从容器上移除,进入下一个页面fromView
,即还停留在原来的页面上。/// 转场动画
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {// 1. get data for animation (获取动画需要的数据)......let toViewStartFrame = transitionContext.initialFrame(for: toVC)var fromViewFinalFrame = transitionContext.finalFrame(for: fromVC)// 2. calculate the value what you want (计算初始位置+最终位置)fromViewFinalFrame = CGRect(x: CGRectGetWidth(containerFrame),y: CGRectGetHeight(containerFrame),width: CGRectGetWidth(fromView.frame),height: CGRectGetHeight(fromView.frame))// 3. Add do toView to the contenerView, and set the initial value (添加 toView 到 contianerView 上, 并设置初始值)containerView.addSubview(toView)toView.frame = toViewStartFrame;// 4. execute animation 执行动画UIView.animate(withDuration: self.transitionDuration(using: transitionContext)) {// 5.1 set final frame for animation viewfromView.frame = fromViewFinalFrame} completion: { finish in// 5.2 get animation resultlet success = !transitionContext.transitionWasCancelled// 5.2.1 remove the view after animation finishif success {fromView.removeFromSuperview()}transitionContext.completeTransition(success)}
}
消失动画里需要注意的是,如果是pop
是能拿到toView
,但如果是dimiss
是拿不到toView
的。
present
和dismiss
动画如果想在一个中间的viewController
进行,则在实现UIViewControllerTransitioningDelegate
协议时,不要实现以上2个返回animator
的方法,而是实现以下返回UIPresentationController
的方法:
// 返回实现 present-dismiss 动效的VC
func presentationController(forPresented presented: UIViewController,presenting: UIViewController?,source: UIViewController) -> UIPresentationController? {return a presentation controller // 实现 present-dismiss 动画的对象
}
官方这个例子主要的动画是设置presentVC
的frame
,frameOfPresentedViewInContainerView
是present
的finalframe
,是dismiss
的initialFrame
。
然后在presentationTransitionWillBegin
和dismissalTransitionWillBegin
方法里执行的动画,仅是添加了一个偏暗的背景View,然后调整alpha
动画显示
和消失
。
效果如如下:
// presentVC 在动画容器上的 frame
override var frameOfPresentedViewInContainerView: CGRect {get { let containerBounds: CGRect = self.containerView?.bounds ?? .zerolet width = CGFloat(floorf(Float(containerBounds.size.width) / 2.0))let height = containerBounds.size.heightlet originX = containerBounds.size.width - widthreturn CGRect(x: originX, y: 0.0, width: width, height: height)}
}
// 暗色背景
lazy var moDimmingView: UIView = {let view = UIView(frame: .zero)view.backgroundColor = UIColor(white: 0.0, alpha: 0.4)view.alpha = 0.0return view
}()
// MARK: - 将要开始 present,设置初始值 和 动画回调
override func presentationTransitionWillBegin() {super.presentationTransitionWillBegin() // 1. get animation container view (获取动画容器视图)guard let containerView = containerView else { return }// 2. set initial value for animation views and add to container view (设置动画视图的初始值, 并添加到都到容器上)self.moDimmingView.frame = containerView.boundsself.moDimmingView.alpha = 0.0containerView.insertSubview(self.moDimmingView, at: 0)// 3. execute animation (执行动画)// 这里尝试去拿一个时间点的回调,能拿到就在回调里执行显示动画;拿不到就直接设置显示guard let transitionCoordinator = self.presentedViewController.transitionCoordinator else {self.moDimmingView.alpha = 1.0return}transitionCoordinator.animateAlongsideTransition(in: self.presentedView) { context inself.moDimmingView.alpha = 1.0}
}// MARK: - present 动画结束
override func presentationTransitionDidEnd(_ completed: Bool) {super.presentationTransitionDidEnd(completed) // remove dark background view when transition failif !completed {self.moDimmingView.removeFromSuperview()}
}
// MARK: - 将要开始 dismiss,设置初始值 和 动画回调
override func dismissalTransitionWillBegin() {super.dismissalTransitionWillBegin() guard let transitionCoordinator = self.presentedViewController.transitionCoordinator else {self.moDimmingView.alpha = 0.0return}transitionCoordinator.animateAlongsideTransition(in: self.presentedView) { context inself.moDimmingView.alpha = 0.0}
}// MARK: - dismiss 动画结束
override func dismissalTransitionDidEnd(_ completed: Bool) {super.dismissalTransitionDidEnd(completed) if completed {self.moDimmingView.removeFromSuperview()}
}
以上,参照官方的例子,可以根据需要写出想要的动画
Demo:github address
参考:
Customizing the Transition Animations
Creating Custom Presentations
上一篇:内存和函数
下一篇:现代密码学导论-20-流密码