来源
https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/CustomizingtheTransitionAnimations.html#//apple_ref/doc/uid/TP40007457-CH16-SW1
过渡动画是把两个controller的内容交换。有两种交换类型:
- presentations:加一个新的view controller到你app的 view controller层级。
- dismissals:从app里移除一个或多个view controller。
the transitioning delegate
transitioning delegate是你自定义呈现和过渡动画的起点。the transitioning delegate是一个你定义的并遵守UIViewControllerTransitioningDelegate协议的对象。它提供给UIKit以下对象:
- animator objects:它是创建动画,并用来展示或隐藏一个view controller的view.这个transitioning delegate可以分别提供animator object 给presenting 和dismissing. animator object 遵守UIViewControllerAnimatedTransitioning协议。
- interactive animator objects:这个对象可以用事件或手势和驱动自定义动画的时间。它遵守UIViewControllerInteractiveTransitioning协议。
最简单的创建一个interaction animator 是继承UIPercentDrivenInteractiveTransition 类,并加事件处理代码到你的子类里。这个类控制了你的动画时间(这个动画是用你已存在的animator objects创建的)。如果你要自己创建你自己的interaction animator,你必须自己来渲染动画里的每一帧(还是不要这样做--PPF)。 - presentation controller: 当一个view controller展示在屏幕上时, presentation controller管理着展示方式。系统为内置的展示类型提供了presentation controller,并且你也可以为您自己的展示类型提供自定义的presentation controller(这部分在下一篇会讲--PPF)。
给 view controller的transitioningDelegate属性赋值一个transitioning delegate,用于告诉UIKit 你要执行一个自定义的过渡和展示方式。你的delegate可以选择提供哪个对象。如果你不提供animator objects,UIKit会使用标准的过渡动画,在view controller的modalTransitionStyle的属性里。
只有当view controller的modalpresentationStype的属性被设为UIModelPresentationCustom时,自定义的presentation controller才会被使用。
自定义动画的调用顺序。
当被呈现的view controller的transitioningDelegate属性包含一个有效值时,UIKit就会用你提供的动画对象(animator objects)来呈现那个view controller.当它已经准备好呈现时,UIKit就会调用transition delegate里的 animationControllerForPresentedController:presentingController:sourceController:方法来接收一个动画对象(animator object).如果对象有效,UIKit就会执行以下步骤:
- UIKit 会调用transitioning delegate的interactionControllerForPresentation:方法去看一下是否有一个interactive animator(交互动画对象)有效。如果是返回nil,UIKit就会执行一个没有用户交互的动画。
- UIKit会调用animator object的 transitionDuration:得到动画持续时间。
- UIKit会调用以下一个合适的方法开始动画:
- 没有interactive animations,UIKit会调用 animator 对象的 animateTransition:。
- 有interactive animations,UIKit会调用interactive animator对象的 startInteractiveTransition:
- UIKit会等着,等着animator对象的context transitioning对象调用 completeTransition: 。
你自己的自定义的animator会在动画结束后调用这个方法,一般会在动画结束块里(completion block)。调用这个方法来结束过渡,并且让UIKit知道这可以调用presentViewController:animated:completion: 的completion handler(结束块)和调用animator 对象自己的animationEnded:方法。
当dismissing一个view controller时,UIKit会调用你的transitioning delegate的animationControllerForDismissedController:并执行以下步骤:
- UIKit调用transition delegate的interactionControllerForDismissal:方法去看一下是否有一个有效的interactive animator(交互动画对象)。如果是返回nil,UIKit就会执行一个没有用户交互的动画。
- UIKit会调用animator object的 transitionDuration:得到动画持续时间。
- UIKit会调用以下一个合适的方法开始动画:
- 没有interactive animations,UIKit会调用 animator 对象的 animateTransition:。
- 有interactive animations,UIKit会调用interactive animator对象的
startInteractiveTransition:
- UIKit会等着,等着animator对象的context transitioning对象调用
completeTransition:
。
你自己的自定义的animator会在动画结束后调用这个方法,一般会在动画结束块里(completion block)。调用这个方法来结束过渡,并且让UIKit知道这可以调用presentViewController:animated:completion:
的completion handler(结束块)和调用animator 对象自己的animationEnded:
方法。
注意:
必须在你的动画结束时调
用completeTransition:
。UIKit不会结束过渡过程,并因此把控制权还给你的app,直到你调用了这个方法。
The Transitioning Context Object
注意:
当你设置自定义的动画时,一定要只使用在transitioning context对象里的对象和数据,而不是你自己管理的缓存数据。过渡是会发生多种多样的情况下,而有一些情况可能会改变动画参数。the transitioning context对象保证会有你需要的正确信息来执行动画,然而你自己的缓存的信息有可能在动画方法被调用时就失效了(不新鲜了)
Presenting a View Controller Using Custom Animations
用自定义的动画要呈现一个新的view controller, 在已存在的view controller的活动方法里加以下步聚:
- 创建一个你要呈现的view controller.
- 创建你自己的transitioning delegate对象,并把它赋给新view controller的
transitioningDelegate
属性。这个transitioning delegate的方法会在需要的时候提供你自定义的animator对象。 - 调用
presentViewController:animated:completion:
方法,呈现新的view controller。
试一下
如果已经有一个segue了,以上的步骤是否可以在
prepareForSegue:
里进行。
Getting the Animation Parameters
- 调用
viewControllerForKey:
分别得到“from"和”to“两个view controller. - 调用
containerView
方法得到动画的父视图。加入所有的关键subView到这里。比如:加入presented view controller的view。 - 调用
viewForKey:
方法得到要加入或移除的view. - 调用
finalFrameForViewController:
方法得到被加入或移除的view的最终frame。
- from:总是在过渡开始时在屏幕上的view controller.
- to:总是在过渡结束时在屏幕上的view controller.
动画的主要目标:
- presentation:加入”to“view 到container view。
- dismissal: 从container view里移除”from“view。
Creating the Transition Animations
presentation animations:
使用
viewControllerForKey:
和viewForKey:
方法得到view controllers 和views。设置 "to"view的开始位置。还有其它属性的起始值(比如transform)。
用
finalFrameForViewController:
方法得到"to"view的结束位置。把"to"view作为一个subview加入container view.
创建动画:
在你的动画块里,在container view里把"to"view移动到它的最终位置。设置最终值到它的其它属性里。
在动画结束块里(
completion block
)里,调用completeTransition:
方法,执行其它清理工作。Dismissal animations:
使用
viewControllerForKey:
和viewForKey:
方法得到view controllers 和views。计算"from"view的结束位置。这个presented view controller里的view 现在是要被dismissed。
把"to"view作为一个subview加入container view.
在过渡结束时,presenting view controller的view("from" view)会被移除。在dismissal过程中,你必须把它加回到container view。创建动画:
在你的动画块里,在container view里把"from"view移动到它的最终位置。设置最终值到它的其它属性里。
在动画结束块里(
completion block
)里,移除"from" view,调用completeTransition:
方法,执行其它清理工作。
import UIKit
class PresentationAnimator: NSObject,UIViewControllerAnimatedTransitioning {
// true:presenting false:dismissable
let presenting:Bool
init(presenting:Bool) {
self.presenting = presenting
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 1.5
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
//获取关联的对象
let containerView = transitionContext.containerView
let fromVC = transitionContext.viewController(forKey: .from)!
let toVC = transitionContext.viewController(forKey: .to)!
let toV = transitionContext.view(forKey: .to)!
let fromV = transitionContext.view(forKey: .from)!
//设置一些动画的变量。
let containerFrame = containerView.frame
var toVStartFrame = transitionContext.initialFrame(for: toVC)
let toVFinalFrame = transitionContext.finalFrame(for: toVC)
var fromVFinalFrame = transitionContext.finalFrame(for: fromVC)
if self.presenting {
toVStartFrame.origin.x = containerFrame.width
toVStartFrame.origin.y = containerFrame.height
containerView.addSubview(toV)
toV.frame = toVStartFrame
}else{
fromVFinalFrame.origin.x = containerFrame.width
fromVFinalFrame.origin.y = containerFrame.height
fromVFinalFrame.size.width = toV.frame.size.width
fromVFinalFrame.size.height = toV.frame.size.height
containerView.insertSubview(toV, at: 0)
toV.frame = toVFinalFrame
}
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
if self.presenting{
toV.frame = toVFinalFrame
}else{
fromV.frame = fromVFinalFrame
}
}) { (_) in
let success = !transitionContext.transitionWasCancelled
// After a failed presentation or successful dismissal, remove the view.
if (self.presenting && !success) || (!self.presenting && success){
toV.removeFromSuperview()
}
transitionContext.completeTransition(success)
}
}
}
中间还有一段interface animator 等以后再看看........
以上的东西也可以用在navigation controller上。push pop.
self.navigationController?.delegate = self
let percentDriven = UIPercentDrivenInteractiveTransition()
extension ViewController:UINavigationControllerDelegate{
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
switch operation {
case .push:
//animator object
return PresentationAnimator(presenting: true)
case .pop:
return PresentationAnimator(presenting: false)
default:
return nil
}
}
}
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return percentDriven;
}