从 iOS 7 开始,自定义转场动画变得非常方便,无论是以模态形式呈现视图控制器,还是使用导航控制器或是选项卡控制器,都可以实现自定义转场动画。即使是自定义的视图控制器容器,只要做一些额外的工作,也能实现自定义转场动画。
此篇笔记主要是结合相关文档对实现自定义转场动画的过程进行梳理,包含如下内容:
1.自定义转场动画涉及的协议
2. 自定义转场动画时的流程
3.实现自定义转场动画
(1) 实现转场代理
(2) 实现转场动画对象
a.获取动画参数
b.创建转场动画
c.在转场动画结束后执行清理工作
4.实现转场交互对象
5. 在转场过程中执行额外的动画
一. 自定义转场动画涉及的协议
实现自定义转场动画主要涉及到如下几个协议:
UIViewControllerAnimatedTransitioning
UIViewControllerInteractiveTransitioning
UIViewControllerContextTransitioning
UIViewControllerTransitioningDelegate & UINavigationControllerDelegate & UITabBarControllerDelegate
UIViewControllerAnimatedTransitioning:
此协议中的方法旨在定义转场动画持续时间以及具体的转场动画效果。需要注意的是,实现此协议创建的转场动画是非交互型转场动画,若要创建交互型转场动画,还需要与实现 UIViewControllerInteractiveTransitioning 协议的转场交互对象相互配合。
实现此协议时,实现 transitionDuration: 方法来指定转场动画持续时间,实现 animateTransition: 方法来实现转场动画效果。转场动画涉及到的相关对象的信息会由实现 UIViewControllerContextTransitioning 协议的转场上下文对象封装,并作为参数传入。
以模态视图形式呈现目标视图控制器时,为其设置 transitioningDelegate 属性,即转场代理。转场代理需实现 UIViewControllerTransitioningDelegate 协议中的相应代理方法来提供相应的转场动画对象和转场交互对象。
UIViewControllerInteractiveTransitioning:
若要实现交互型转场动画,需利用实现此协议的转场交互对象与实现 UIViewControllerAnimatedTransitioning 协议的转场动画对象相互配合。此协议中的方法旨在根据用户交互控制转场动画时间,具体的转场动画效果则需由转场动画对象提供。
UIPercentDrivenInteractiveTransition 这个类已经实现了此协议,可以直接拿来使用,也可根据需要进行子类化。该类会控制转场动画对象的动画过程,如果不使用该类就只能自己处理动画过程。
UIViewControllerContextTransitioning:
此协议中的方法旨在提供转场过程中的上下文信息。一般无需实现此协议,也不用创建实现此协议的对象。相反,在转场过程中,系统会提供一个实现了此协议的转场上下文对象,转场动画对象和转场交互对象都可以通过相应协议方法的参数来获取转场上下文对象。
转场上下文对象封装了转场过程涉及到的视图控制器和视图的信息。对于交互型转场动画,转场交互对象可以利用此协议中的相关方法来报告转场进度。在交互型转场动画开始时,转场交互对象需要保存转场上下文对象的引用,然后基于用户的交互进度,通过 updateInteractiveTransition:、finishInteractiveTransition、cancelInteractiveTransition 方法来报告转场进度直至转场完成或取消。
注意: 定义转场动画对象时,应该检查转场上下文对象的 isAnimated 方法的返回值,以此决定是否应该执行动画。在动画完成时,应该调用转场上下文对象的 completeTransition: 方法向系统报告转场动画是顺利完成还是被中途取消。
下图展示了转场上下文对象在转场过程中所扮演的角色:
UIViewControllerTransitioningDelegate:
以模态形式呈现视图控制器时,为目标视图控制器设置 transitioningDelegate 属性,并根据需求实现相应的代理方法来提供转场动画对象。若要支持交互型转场动画,则还需提供转场交互对象。
导航控制器和选项卡控制器也支持自定义转场动画以及交互型转场动画,通过如下代理方法提供转场动画对象和转场交互对象即可。
下图展示了转场代理在转场过程中所扮演的角色:
图中蓝色的方块即是目标视图控制器,它通过它的转场代理来提供相应的转场对象。虽然图中展示的是以模态形式呈现视图控制器时的情况,不过使用导航控制器和选项卡控制器时原理与此基本相同。
二. 自定义转场动画时的流程
以下流程仅针对以模态形式呈现视图控制器时的情况,使用导航控制器和选项卡控制器时大同小异。
当目标视图控制器设置了 transitioningDelegate 时,系统会在视图控制器呈现前调用转场代理的 animationControllerForPresentedController:presentingController:sourceController: 方法来获取相应的转场动画对象。如果转场代理提供了转场动画对象,则进入如下流程:
1. 继续调用转场代理的 interactionControllerForPresentation: 方法获取转场交互对象,若不提供,则执行非交互型的转场动画。
2. 调用转场动画对象的 transitionDuration: 方法获取转场动画持续时间。
3. 根据是否是交互型转场动画做不同处理:
对于非交互型转场动画,调用转场动画对象的 animateTransition: 方法。
对于交互型转场动画,调用转场交互对象的 startInteractiveTransition: 方法。
4. 等待转场动画对象调用转场上下文对象的 completeTransition: 方法。通常会在动画的完成闭包中调用此方法。之后,系统就会调用 presentViewController:animated:completion: 方法和转场动画对象的 animationEnded: 方法。
在 dismissing 阶段,系统会调用转场代理的 animationControllerForDismissedController: 方法获取相应的转场动画对象。如果转场代理提供了转场动画对象,则进入如下流程:
1. 继续调用转场代理的 interactionControllerForDismissal: 方法获取转场交互对象,若不提供,则执行非交互型的转场动画。
2. 调用转场动画对象的 transitionDuration: 方法获取转场动画持续时间。
3. 根据是否是交互型转场动画做不同处理:
对于非交互型转场动画,调用转场动画对象的 animateTransition: 方法。
对于交互型转场动画,调用转场交互对象的 startInteractiveTransition: 方法。
4. 等待转场动画对象调用转场上下文对象的 completeTransition: 方法。通常会在动画的完成闭包中调用此方法。之后,系统就会调用 dismissViewControllerAnimated:completion: 方法和转场动画对象的 animationEnded: 方法。
注意: 一定要在动画结束后调用转场上下文对象的 completeTransition: 方法,这样系统才会结束转场过程,并将控制权返还给应用。
三. 实现自定义转场动画
以模态形式呈现视图控制器为例,导航控制器和选项卡控制器与此大同小异,主要步骤如下:
1. 创建要呈现的视图控制器。
2. 为要呈现的视图控制器设置 transitioningDelegate 属性,并通过相关代理方法提供转场动画对象和转场交互对象。
3. 调用 presentViewController:animated:completion: 方法呈现视图控制器。
调用 presentViewController:animated:completion: 方法后,系统会在下一运行循环开始转场过程,该过程一直会持续到转场动画对象调用转场上下文对象的 completeTransition: 方法。交互型转场动画可以在转场过程中处理用户交互,非交互型转场动画只会运行转场动画对象指定的持续时间。
实现转场代理
转场代理的目的是提供转场动画对象和转场交互对象,例如如下代码所示:
其他代理方法与此基本相同。在实际使用中,可能需要根据应用的状态来返回不同的转场动画对象和转场交互对象。
实现转场动画对象
转场动画对象是实现了 UIViewControllerAnimatedTransitioning 协议的对象,关键方法是 animateTransition:,使用此方法来创建执行固定时间的转场动画。转场动画过程大致可分为如下阶段:
1. 获取动画参数。
2. 使用核心动画或视图动画来创建转场动画。
3. 在动画结束后报告动画完成并进行适当的清理工作。
获取动画参数
转场上下文对象会作为参数传入 animateTransition: 方法,应该总是从该上下文对象中获取信息,而不要缓存信息。有时候,转场动画会涉及到视图控制器之外的视图。例如,使用自定义的 UIPresentationController 时,转场动画可能会涉及到额外的背景视图,使用转场上下文能确保获取到正确的视图控制器和视图。
调用 viewControllerForKey: 方法来获取转场动画涉及到的视图控制器,而不要假设哪些视图控制器参与到了转场动画中。
调用 containerView 方法来获取转场动画所在的父视图,转场动画涉及的所有视图都是该视图的子视图。
在 iOS 8 之后,调用 viewForKey: 方法来获取转场动画涉及到的视图,而不要直接使用视图控制器的视图,以确保正确的行为。
调用 finalFrameForViewController: 方法获取视图控制器的视图的最终 frame。
转场上下文对象使用 from 和 to 来描述转场动画涉及到的视图控制器、视图以及视图的 frame。from 描述的是当前在屏幕上的对象,to 描述的则是转场结束后出现在屏幕上的对象。如下图所示:
可以看到,在转场过程的不同阶段,这两个词语所描述的对象也有所不同。
创建转场动画
presentation 阶段:
利用转场上下文对象的 viewControllerForKey: 和 viewForKey: 方法获取相关的视图控制器和视图。
利用转场上下文对象的 initialFrameForViewController 和 finalFrameForViewController: 方法获取 frame。
设置 from 视图和 to 视图在转场动画开始前的动画属性。
将 to 视图添加到 containerView 上。
创建动画。在动画完成时,调用转场上下文对象的 completeTransition: 方法,并根据需要执行清理工作。
dismissal 阶段:
利用转场上下文对象的 viewControllerForKey: 和 viewForKey: 方法获取相关的视图控制器和视图。
利用转场上下文对象的 initialFrameForViewController 和 finalFrameForViewController: 方法获取 frame。
设置 from 视图和 to 视图在转场动画开始前的动画属性。
将 to 视图添加到 containerView 上。因为此视图在 presentation 阶段完成后被移除了。
创建动画。在动画完成时,调用转场上下文对象的 completeTransition: 方法,并根据需要执行清理工作。
下图演示了一种简单的自定义转场动画:
相应代码如下所示:
在转场动画结束后执行清理工作
在转场动画完成后,例如在完成闭包中,调用转场上下文对象的 completeTransition: 方法,告知系统转场动画已经完成。这也将导致 presentViewController:animated:completion: 方法和转场动画对象的 animationEnded: 方法被调用。
由于交互型转场过程可以被取消,因此应该使用转场上下文对象的 transitionWasCancelled 方法进行判断。若转场过程被取消,调用 completeTransition: 方法时则应该传入 false,并根据需要执行一些清理工作,例如将视图层级恢复到转场过程开始之前的状态。
实现转场交互对象
实现转场交互对象的最简单的方法是使用 UIPercentDrivenInteractiveTransition 对象,该对象实现了 UIViewControllerInteractiveTransitioning 协议,能够和转场动画对象配合,根据用户交互控制转场动画过程。选择子类化 UIPercentDrivenInteractiveTransition 对象时,可以在构造方法或者 startInteractiveTransition: 方法中进行初始化设置。在交互过程中,根据用户手势以一定的逻辑来计算转场完成度,并调用 updateInteractiveTransition: 方法进行更新。当判定交互完成时,调用 finishInteractiveTransition 方法。若交互被取消,则调用 cancelInteractiveTransition 方法。
相应代码如下所示:
四. 在转场过程中执行额外的动画
在转场时,可以通过视图控制器的 transitionCoordinator 属性获取一个实现了 UIViewControllerTransitionCoordinator 协议的转场协调员对象。利用转场协调员对象所实现的协议方法,可以在转场过程中执行额外的动画。注意,转场协调员对象只存在于转场过程中,因此不要保存它。可以在视图控制器的 viewWillAppear: 方法中来获取转场协调员对象。
UIViewControllerTransitionCoordinator 协议声明如下:
UIViewControllerTransitionCoordinator 协议的父协议和 UIViewControllerContextTransitioning 协议极其相似:
下图展示了转场协调员对象在转场过程中所扮演的角色: