ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC=5] * * * * * >原文链接 :[Customizing the Transition Animations](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/CustomizingtheTransitionAnimations.html#//apple_ref/doc/uid/TP40007457-CH16-SW1) 转换动画提供关于应用程序界面更改的视觉反馈。 UIKit 在呈现视图控制器时提供了一组标准转换样式,可以使用自定义转换来补充标准转换。 ### 第一节 转换动画流程 转换动画将一个视图控制器的内容转换为另一个视图控制器的内容。有两种类型的转换:显示和关闭。一个表示转换将一个新的视图控制器添加到应用程序的视图控制器层次结构中,而一个被关闭的转换将从层次结构中移除一个或多个视图控制器。 实现转换动画需要很多对象。UIKit 提供了所有与转换相关的对象的默认版本,可以自定义所有这些对象,或者一部分。如果选择了合适的对象,那么应该能够只使用少量的代码来创建动画。如果利用了 UIKit 提供的现有代码,那么即使是包含交互的动画也可以很容易地实现。 #### 1.1 转换代理 转换代理是转换动画和自定义显示的起点。 转换代理是一个对象,由你自定义并且遵受 `UIViewControllerTransitioningDelegate` 协议。它的工作是为 UIKit 提供以下对象: * **Animator objects.** 动画器对象负责创建用于显示或隐藏视图控制器视图的动画。转换代理可以为呈现和关闭视图控制器提供单独的动画器对象。动画器对象遵守 [UIViewControllerAnimatedTransitioning](https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning) 协议。 * **Interactive animator objects.** 交互式动画器对象使用触摸事件或手势识别器来驱动自定义动画的时间。 交互式动画器遵守 [UIViewControllerInteractiveTransitioning](https://developer.apple.com/documentation/uikit/uiviewcontrollerinteractivetransitioning) 协议。 创建交互式动画的最简单的方法是继承 [UIPercentDrivenInteractiveTransition](https://developer.apple.com/documentation/uikit/uipercentdriveninteractivetransition) 类并将事件处理代码添加到您的子类中。 该类控制使用现有的动画对象创建的动画的时间。 如果您创建自己的交互式动画,则必须自己渲染动画的每一帧。 * **Presentation controller.** 显示控制器管理视图控制器在屏幕上显示风格。 系统为内置显示样式提供显示控制器,可以为自己的显示样式提供自定义显示控制器。 有关创建自定义演显示控制器的更多信息,请参阅 [Creating Custom Presentations](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/DefiningCustomPresentations.html#//apple_ref/doc/uid/TP40007457-CH25-SW1) 。 将转换代理赋值视图控制器的 [transitioningDelegate](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621421-transitioningdelegate) 属性会告知 UIKit 要执行自定义转换或显示。 你的代理可以选择它提供的对象。 如果不提供动画对象,则 UIKit 根据视图控制器的 [modalTransitionStyle](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621388-modaltransitionstyle) 属性中选择一个标准转换动画。 图 10-1 显示了转换代理、动画对象与呈现视图控制器的关系。 仅当视图控制器的 [modalPresentationStyle](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621355-modalpresentationstyle) 属性设置为 [UIModalPresentationCustom](https://developer.apple.com/documentation/uikit/uimodalpresentationstyle/1621375-custom) 。 ###### 图 10-1 自定义显示和动画对象 ![](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/Art/VCPG_custom-presentation-and-animator-objects_10-1_2x.png) 有关如何实现转换代理的信息,请参阅 [Implementing the Transitioning Delegate](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/CustomizingtheTransitionAnimations.html#//apple_ref/doc/uid/TP40007457-CH16-SW15)。有关转换代理对象方法的更多信息,请参阅 [UIViewControllerTransitioningDelegate Protocol Reference](https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioningdelegate) 。 #### 1.2 自定义动画流程 当被呈现视图控制器的 [transitioningDelegate](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621421-transitioningdelegate) 属性包含有效对象时,UIKit 会使用提供的自定义动画器对象呈现该视图控制器。 在准备显示效果时,UIKit 会调用转换代理的 [animationControllerForPresentedController:presentsController:sourceController:](https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioningdelegate/1622037-animationcontroller) 方法来检索自定义动画器对象。 如果有对象可用,UIKit 将执行以下步骤: 1. UIKit 调用转换委托的 [interactionControllerForPresentation:](https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioningdelegate/1622050-interactioncontrollerforpresenta) 方法来查看交互式动画对象是否可用。 如果该方法返回 nil ,那么 UIKit 将执行无需用户交互的动画。 2. UIKit 调用动画器对象的 [transitionDuration:](https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning/1622032-transitionduration) 方法来获取动画持续时间。 3. UIKit调用适当的方法来启动动画: * 对于非交互式动画,UIKit 调用动画器对象的 [animateTransition:](https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning/1622061-animatetransition) 方法。 * 对于交互式动画,UIKit 调用交互式动画器对象的 [startInteractiveTransition:](https://developer.apple.com/documentation/uikit/uiviewcontrollerinteractivetransitioning/1622028-startinteractivetransition) 方法。 4. UIKit 等待动画器对象调用 context transitioning 对象的 [completeTransition:](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622042-completetransition) 方法。 自定义动画器对象在动画完成后调用此方法,通常在动画的完成回调代码块中。 调用这个方法结束转换,并让 UIKit 知道它可以调用 [ presentViewController:animated:completion:](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621380-presentviewcontroller) 的完成回调代码块,并调用动画器对象自己的 [animationEnded:](https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning/1622059-animationended) 方法。 关闭视图控制器时,UIKit 会调用转换代理的 [animationControllerForDismissedController:](https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioningdelegate/1622047-animationcontrollerfordismissedc) 方法,并执行以下步骤: 1. UIKit 调用转换委托的 [interactionControllerForDismissal:](https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioningdelegate/1622030-interactioncontrollerfordismissa) 方法来查看交互式动画对象是否可用。 如果该方法返回 nil ,那么 UIKit 将执行无需用户交互的动画。 2. UIKit 调用动画器对象的 [transitionDuration:](https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning/1622032-transitionduration) 方法来获取动画持续时间。 3. UIKit调用适当的方法来启动动画: * 对于非交互式动画,UIKit 调用动画器对象的 [animateTransition:](https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning/1622061-animatetransition) 方法。 * 对于交互式动画,UIKit 调用交互式动画器对象的 [startInteractiveTransition:](https://developer.apple.com/documentation/uikit/uiviewcontrollerinteractivetransitioning/1622028-startinteractivetransition) 方法。 4. UIKit 等待动画器对象调用 context transitioning 对象的 [completeTransition:](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622042-completetransition) 方法。 自定义动画器对象在动画完成后调用此方法,通常在动画的完成回调代码块中。 调用这个方法结束转换,并让 UIKit 知道它可以调用 [ presentViewController:animated:completion:](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621380-presentviewcontroller) 的完成回调代码块,并调用动画器对象自己的 [animationEnded:](https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning/1622059-animationended) 方法。 > 重要提示 在动画结束时必需调用 [completeTransition:](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622042-completetransition) 方法。 UIKit 不会结束转换过程,从而将控制返回到应用程序,直到调用该方法。 #### 1.3 转换上下文对象 在转换动画开始之前,UIKit 创建一个转换的上下文对象并填充关于如何执行动画的信息。 转换上下文对象是您的代码的重要组成部分。 它实现了 [UIViewControllerContextTransitioning](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning) 协议,并存储转换中涉及的视图控制器和视图的引用。 它还存储有关如何执行转换的信息,包括动画是否是交互式的。 动画器对象需要所有这些信息来设置和执行实际的动画。 > 重要提示 设置自定义动画时,请始终使用转换上下文对象中的对象和数据,而不要使用自己管理的任何缓存信息。 转换可能发生在各种条件下,其中一些可能会改变动画参数。 转换的上下文对象保证有正确的信息来执行动画,而你的动画器的方法被调用的时候,你的缓存信息可能是无效的。 图 10-2 显示了转换上下文对象如何与其他对象进行交互。 动画器对象在其 [animateTransition:](https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning/1622061-animatetransition) 方法中接收该对象。 创建的动画应该在提供的容器视图中进行。 例如,在呈现视图控制器时,将其视图添加为容器视图的子视图。 容器视图可能是窗口或常规视图,但始终配置为运行您的动画。 ###### 图 10-2 转换上下文对象 ![](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/Art/VCPG_transitioning-context-object_10-2_2x.png) 有关转换上下文对象的更多信息,请参见 [UIViewControllerContextTransitioning Protocol Reference](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning) 。 #### 1.4 转换协调器 对于内置转换和自定义转换,UIKit 都会创建一个转换协调器对象,以方便可能需要执行的任何额外动画。 除了呈现和关闭视图控制器,当发生界面旋转时或视图控制器的大小位置更改时,可能会发生转换。 所有这些转换都代表视图层次结构的变化。转换协调器是一种跟踪这些变化并同时动画自己的内容的方式。 要访问转换协调器,请获取受影响的视图控制器的 [transitionCoordinator](https://developer.apple.com/documentation/uikit/uiviewcontroller/1619294-transitioncoordinator) 属性中的对象。 转换协调器仅在转换期间存在。 图 10-3 显示了转换协调器与转换中涉及的视图控制器之间的关系。使用过渡协调器获取有关转换的信息,并注册要与转换动画同时执行的动画代码块。转换协调器对象遵守 UIViewControllerTransitionCoordinatorContext 协议,该协议提供时序信息,动画当前状态的有关信息以及转换中涉及的视图和视图控制器。当动画块被执行时,它们都会收到一个具有相同信息的上下文对象。 ###### 图 10-3 转换协调器对象 ![](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/Art/VCPG_transition-coordinator-objects_10-3_2x.png) 有关转换协调器对象的更多信息,请参阅 [UIViewControllerTransitionCoordinator Protocol Reference](https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioncoordinator) 。 有关可用于配置动画的上下文信息的信息,请参阅 [UIViewControllerTransitionCoordinatorContext Protocol Reference](https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioncoordinatorcontext) 。 ### 第二节 使用自定义动画呈现视图控制器 要使用自定义动画呈现视图控制器,请在现有视图控制器的操作方法中执行以下操作: 1. 创建想要呈现的视图控制器 2. 创建自定义转换代理对象,并将其分配给视图控制器的 [transitioningDelegate](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621421-transitioningdelegate) 属性。 转换代理的方法应该在被调用时创建并返回自定义的动画器对象。 3. 调用 [presentViewController:animated:completion:](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621380-presentviewcontroller) 方法来呈现视图控制器。 当调用 [presentViewController:animated:completion:](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621380-presentviewcontroller) 方法时,UIKit 将启动转换过程。 转换在下一次运行循环迭代期间开始并继续,直到自定义动画器调用 [completeTransition:](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622042-completetransition) 方法。 交互式转换允许在转换过程中处理触摸事件,但非交互式转换会在动画器对象指定的持续时间内运行。 ### 第三节 实现转换代理 转换代理的目的是创建并返回自定义对象。 清单 10-1 显示了转换方法的实现是多么的简单。 这个例子创建并返回一个自定义的动画器对象。 实际上大部分的工作都是由动画器对象本身来处理的。 ###### 清单 10-1 创建一个动画器对象 ~~~ - (id<UIViewControllerAnimatedTransitioning>) animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { MyAnimator* animator = [[MyAnimator alloc] init]; return animator; } ~~~ 转换代理的其他方法可以像前面的清单一样简单。还可以自定义逻辑来基于应用程序的当前状态返回不同的动画器对象。 有关转换委托方法的更多信息,请参阅 [UIViewControllerTransitioningDelegate Protocol Reference](https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioningdelegate) 。 ### 第四节 实现动画器对象 动画器对象是遵守 [UIViewControllerAnimatedTransitioning](https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning) 协议的任何对象。动画器对象创建在固定时间内执行的动画。动画器对象的关键是它的 [animateTransition:](https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning/1622061-animatetransition) 方法,用于创建实际的动画。动画的过程大致分为以下几个部分: 1. 获取动画参数 2. 使用 Core Animation 或 UIView Animation 方法创建动画 3. 清理并完成转换 #### 4.1 获取动画参数 传递给 [animateTransition:](https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning/1622061-animatetransition) 方法的上下文转换对象包含执行动画时要使用的数据。从上下文转换对象获取更多最新信息时,切勿使用自己的缓存信息或从视图控制器获取信息。呈现和关闭视图控制器有时会涉及视图控制器之外的对象。例如,自定义的转换控制器可能会为转换添加一个背景视图。 上下文转换对象会考虑额外的视图和对象,并为您提供正确的动画视图。 * 调用 [viewControllerForKey:](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622043-viewcontroller) 方法两次,以获得转换中涉及的“from”和“to”视图控制器。永远不要假设你知道哪些视图控制器参与了转换。UIKit 可能会改变视图控制器以便适应新的特性环境或响应应用程序的请求。 * 调用 [containerView](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622045-containerview) 方法来获取动画的父视图。 将所有关键子视图添加到此视图。 例如,在转换期间,将被呈现的视图控制器的视图添加到此视图。 * 调用 [viewForKey:](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622055-viewforkey) 方法来获取被添加或删除的视图。视图控制器的视图可能不是唯一在转换期间添加或删除的视图。转换控制器可以将视图插入到必须添加或删除的层次结构中。 [viewForKey:](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622055-viewforkey) 方法返回包含你需要添加或删除的所有内容的根视图。 * 调用 [finalFrameForViewController:](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622024-finalframeforviewcontroller) 方法来获取被添加或删除视图的最终大小位置。 上下文转换对象使用“from”和“to”命名,以识别转换过程中涉及的视图控制器、视图和 frame 。“from”视图控制器始终是在转换开始时屏幕上显示的视图控制器,而“to”视图控制器是在转换结束时可以看到的视图控制器。正如在图 10-4 中所看到的,“from”和“to”视图控制器在显示和被关闭之间交换位置。 ###### 图 10-4 from 和 to对象 ![](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/Art/VCPG_from-and-to-objects_10-4_2x.png) 交换这些值可以更容易地编写一个处理显示和关闭的动画器。 当设计动画器时,所要做的就是包含一个属性来知道它是显示动画还是关闭动画。 两者之间唯一需要的区别如下: * 对于显示,请将“to”视图添加到容器视图层次结构中。 * 对于关闭,从容器视图层次结构中删除“from”视图。 #### 4.2 创建转换动画 在一个典型的转换中,属于被呈现的视图控制器的视图会被以动画移动到目标位置。其他视图可以作为转换的一部分进行动画,但是动画的主要目标是将视图添加到视图层次结构中。 动画主视图时,您配置动画的基本操作是相同的。 您可以从转换的上下文对象中获取所需的对象和数据,并使用该信息创建实际的动画。 * 显示动画: * 使用 [viewControllerForKey:](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622043-viewcontroller) 和 [viewForKey:](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622055-viewforkey) 方法来检索转换中涉及的视图控制器和视图。 * 设置“to”视图的起始位置。 将其他任何属性也设置为它们的起始值。 * 从转换上下文的 [finalFrameForViewController:](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622024-finalframeforviewcontroller) 方法获取“to”视图的结束位置。 * 将“to”视图添加为容器视图的子视图。 * 创建动画。 * 在动画代码块中,将“to”视图移动到容器视图的最终位置。将任何其他属性设置为它们的最终值。 * 在完成代码块中,调用 [completeTransition:](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622042-completetransition) 方法,并执行清理工作。 * 关闭动画: * 使用 [viewControllerForKey:](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622043-viewcontroller) 和 [viewForKey:](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622055-viewforkey) 方法来检索转换中涉及的视图控制器和视图。 * 计算“from”视图的结束位置。这个视图属于已经被解散的视图控制器。 * 将“to”视图添加为容器视图的子视图。 在显示期间,当转换完成时,属于呈现视图控制器的视图将被删除。因此,在一次关闭操作中,必须将该视图添加到容器中。 * 创建动画。 * 在动画代码块中,将“from”视图移动到容器视图的最终位置。将任何其他属性设置为它们的最终值。 * 在完成代码块中,从视图层次中删除“from”视图,调用 [completeTransition:](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622042-completetransition) 方法,并根据需要执行清理工作。 图 10-5 显示了一个自定义的显示和关闭转换,可以对角地呈现视图。 在转换过程中,呈现的视图从屏幕外开始,向左上角方向移动,直到可见。 在关闭的过程中,视图反转了方向,向右下角移动,直到再次离屏。 ###### 图 10-5 自定义显示和关闭 ![](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/Art/VCPG_custom-presentation-and-dismissal_10-5_2x.png) 清单 10-2 展示了如何实现图 10-5 中所示的转换。在获取动画所需的对象之后,[animateTransition:](https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning/1622061-animatetransition) 方法计算受影响视图的 frame 。在显示过程中,被呈现的视图由 `toView` 变量表示。在关闭过程中,被关闭的视图由 `fromView` 变量表示。`presenting`属性是动画器对象本身的自定义属性,当创建动画器时,转换代理设置为适当的值。 ###### 清单 10-2 实现对角显示和关闭的动画 ~~~ - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { // 获取相关对象 (Get the set of relevant objects) UIView *containerView = [transitionContext containerView]; UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey]; UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; // 设置动画所需变量 (Set up some variables for the animation) CGRect containerFrame = containerView.frame; CGRect toViewStartFrame = [transitionContext initialFrameForViewController:toVC]; CGRect toViewFinalFrame = [transitionContext finalFrameForViewController:toVC]; CGRect fromViewFinalFrame = [transitionContext finalFrameForViewController:fromVC]; // 设置动画参数 (Set up the animation parameters) if (self.presenting) { // 修改被呈现的视图的 frame,使其从屏幕右下角的容器开始(Modify the frame of the presented view so that it starts offscreen at the lower-right corner of the container) toViewStartFrame.origin.x = containerFrame.size.width; toViewStartFrame.origin.y = containerFrame.size.height; } else { // 修改关闭视图的 frame,使其在容器视图的右下角结束(Modify the frame of the dismissed view so it ends in the lower-right corner of the container view.) fromViewFinalFrame = CGRectMake(containerFrame.size.width, containerFrame.size.height, toView.frame.size.width, toView.frame.size.height); } // 总是将“to”视图添加到容器中 (Always add the "to" view to the container) // 设置它的起始帧 (And it doesn't hurt to set its start frame) [containerView addSubview:toView]; toView.frame = toViewStartFrame; // 使用动画器返回动画时间 (Animate using the animator's own duration value) [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ if (self.presenting) { // 移动被显示视图到最终位置 (Move the presented view into position) [toView setFrame:toViewFinalFrame]; } else { // 被关闭视图移除屏幕 (Move the dismissed view offscreen) [fromView setFrame:fromViewFinalFrame]; } } completion:^(BOOL finished){ BOOL success = ![transitionContext transitionWasCancelled]; // 显示失败或者关闭之后移除视图 (After a failed presentation or successful dismissal, remove the view) if ((self.presenting && !success) || (!self.presenting && success)) { [toView removeFromSuperview]; } //通知 UIKit 转换结束 (Notify UIKit that the transition has finished) [transitionContext completeTransition:success]; }]; } ~~~ #### 4.3 动画完成后进行清理 在转换动画结束时,调用 [completeTransition:](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622042-completetransition) 方法至关重要。 调用这个方法会告诉 UIKit 转换已经完成,用户可能会开始使用被呈现的视图控制器。调用该方法也引发一连串的其他完成回调方法,包括一个来自 [presentViewController:animated:completion:](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621380-presentviewcontroller) 方法和动画器对象的 [animationEnded: ](https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning/1622059-animationended)方法。调用 [completeTransition:](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622042-completetransition) 方法最佳位置是在动画代码块的完成回调方法中。 因为转换可以被取消,所以应该使用上下文对象的 [transitionWasCancelled](https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning/1622039-transitionwascancelled) 方法的返回值来确定需要进行哪些清理工作。当一个显示被取消时,动画器必须撤销它对视图层次结构所做的任何修改。成功的关闭需要采取类似的行动。 ### 第五节 为转换添加交互性 使用交互动画最简单的方法是使用 [UIPercentDrivenInteractiveTransition](https://developer.apple.com/documentation/uikit/uipercentdriveninteractivetransition) 对象。`UIPercentDrivenInteractiveTransition` 对象与现有的动画器对象一起工作来控制动画的时间。 使用提供的完成进度百分比值执行此操作。 所要做的就是设置事件处理代码,以计算完成百分比值,并在每个新事件到达时更新它。 可以使用 `UIPercentDrivenInteractiveTransition` 类或其子类。如果是子类,则使用子类的 `init` 方法(或 `startInteractiveTransition:` 方法)执行事件处理代码的一次性设置。之后,使用自定义的事件处理代码来计算新的完成百分比值,并调用 `updateInteractiveTransition:` 方法。当你的代码确定转换完成时,调用 `finishInteractiveTransition` 方法。 清单 10-3 显示了 `UIPercentDrivenInteractiveTransition` 子类的 `startInteractiveTransition:`方法的自定义实现。此方法设置了一个拖动手势识别器来跟踪触摸事件,并在动画的容器视图上安装该手势识别器。它还保存了稍后使用的转换上下文的引用。 ###### 清单 10-3 配置一个百分比驱动的交互式动画 ~~~ - (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext { // 调用父类初始化方法(Always call super first) [super startInteractiveTransition:transitionContext]; // 保存转换上下文引用(Save the transition context for future reference) self.contextData = transitionContext; // 创建拖动手势跟在触摸事件(Create a pan gesture recognizer to monitor events) self.panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeUpdate:)]; self.panGesture.maximumNumberOfTouches = 1; //容器视图上安装该手势识别器( Add the gesture recognizer to the container view) UIView* container = [transitionContext containerView]; [container addGestureRecognizer:self.panGesture]; } ~~~ 手势识别器为每个到达的新事件调用其操作方法。 操作方法的实现可以使用手势识别器的状态信息来确定手势是成功、失败还是仍在进行中。 同时,可以使用最新的触摸事件信息来计算手势的新百分比值。 清单 10-4 显示了清单 10-3 中配置的拖动手势识别器所调用的方法。 当新事件到达时,此方法使用垂直方向上的移动距离来计算动画的完成百分比。 当手势结束时,该方法结束转换。 ###### 清单 10-4 使用事件来更新动画进度 ~~~ -(void)handleSwipeUpdate:(UIGestureRecognizer *)gestureRecognizer { UIView* container = [self.contextData containerView]; if (gestureRecognizer.state == UIGestureRecognizerStateBegan) { // 重置手势开始时的进度值(Reset the translation value at the beginning of the gesture) [self.panGesture setTranslation:CGPointMake(0, 0) inView:container]; } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged) { // 获取当前进度值(Get the current translation value) CGPoint translation = [self.panGesture translationInView:container]; // 计算手势相对于容器视图的高度垂直移动的距离(Compute how far the gesture has travelled vertically relative to the height of the container view.) CGFloat percentage = fabs(translation.y / CGRectGetHeight(container.bounds)); // 使用百分比进度值更新动画器 (Use the translation value to update the interactive animator) [self updateInteractiveTransition:percentage]; } else if (gestureRecognizer.state >= UIGestureRecognizerStateEnded) { // 结束转换,移除手势 (Finish the transition and remove the gesture recognizer) [self finishInteractiveTransition]; [[self.contextData containerView] removeGestureRecognizer:self.panGesture]; } } ~~~ > 注意 计算的值代表整个动画长度的完成百分比。 对于交互式动画,可能需要避免非线性效应,例如动画本身的初始速度,阻尼值和非线性完成曲线。 这样的效果往往会将事件的触摸位置与底层视图的移动分离。 ### 第六节 创建与转换同时运行的动画 参与转换的视图控制器可以在任何显示或转换动画的基础上执行其他动画。例如,被呈现的视图控制器可以在转换期间对其自己的视图层次结构进行动画处理,并在转换发生时添加运动效果或其他视觉反馈。任何对象都可以创建动画,只要它能够访问被呈现或呈现视图控制器的 [transitionCoordinator](https://developer.apple.com/documentation/uikit/uiviewcontroller/1619294-transitioncoordinator) 属性即可。转换协调员器只有在正在进行转换时才存在。 请调用转换协调器的 [animateAlongsideTransition:completion:](https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioncoordinator/1619300-animate) 或 [animateAlongsideTransitionInView:animation:completion:](https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioncoordinator/1619295-animatealongsidetransitioninview) 方法来创建动画。提供的代码块将被存储,直到转换动画开始,此时它们将与其余的转换动画一起执行。 ### 第七节 使用呈现控制器处理动画 对于自定义转换,可以提供自己的转换控制器,使被呈现视图控制器具有自定义外观。转换控制器管理与视图控制器及其内容分离的任何自定义镶边(过渡效果)。例如,放置在视图控制器视图后面的蒙版视图将由转换控制器管理。它没有管理特定视图控制器的视图,这意味着可以在的应用中使用相同的转换控制器。 可以从被呈现视图控制的转换代理提供自定义转换控制器。(视图控制器的 [modalTransitionStyle](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621388-modaltransitionstyle) 属性必须是 [UIModalPresentationCustom](https://developer.apple.com/documentation/uikit/uimodalpresentationstyle/1621375-custom) 。)转换控制器与动画器对象并行运行。由于动画器将对视图控制器的视图进行动画处理,所以表示控制器可将任何其他视图动画移动到目标位置。在转换结束时,转换控制器有机会对视图层次结构执行最终的调整。 有关如何创建自定义转换控制器的信息,请参阅 [Creating Custom Presentations](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/DefiningCustomPresentations.html#//apple_ref/doc/uid/TP40007457-CH25-SW1) 。