代码改变世界

笔记-iOS 视图控制器转场详解(上)

2016-03-20 17:51  JG2014  阅读(236)  评论(0编辑  收藏  举报

这是一篇长文,详细讲解了视图控制器转场的方方面面,配有详细的示意图和代码,为了使得文章在微信公众号中易于阅读,seedante 辛苦将大量长篇代码用截图的方式呈现,另外作者也在 Github 上附上了完整的示例代码,满满的诚意之作。

作者 seedante 是一个低调人士,只愿意透露他的 GitHub:https://github.com/seedante。感谢作者授权微信独家代理,本文的所有打赏归 seedante 所有。

 

前言

本文并非华丽的转场动画教程,相反,文中的转场动画效果都十分简单,但本文的内容并不简单,我将带你探索转场背后的机制,缺陷以及实现过程中的技巧与陷阱。阅读本文需要读者至少要对 ViewController 和 View 的结构以及协议有基本的了解,最好自己亲手实现过一两种转场动画。

如果你对此感觉没有信心,推荐观看官方文档:View Controller Programming Guide for iOS,学习此文档将会让你更容易理解本文的内容。对你想学习的小节,我希望你自己亲手写下这些代码,一步步地看着效果是如何实现的,至少对我而言,看各种相关资料时只有字面意义上的理解,正是一步步的试验才能让我理解每一个步骤。

本文涉及的内容较多,为了避免篇幅过长,我只给出关键代码而不是从新建工程开始教你每一个步骤。本文基于 Xcode 7 以及 Swift 2,Demo 合集地址:iOS-ViewController-Transition-Demo。

文章目录:

上篇:

 

下篇:

 

Transition 解释

前言里从行为上解释了转场,那在转场时发生了什么?下图是从 WWDC 2013 Session 218 整理的,解释了转场时视图控制器和其对应的视图在结构上的变化:

 

转场过程中,作为容器的父 VC 维护着多个子 VC,但在视图结构上,只保留一个子 VC 的视图,所以转场的本质是下一场景(子 VC)的视图替换当前场景(子 VC)的视图以及相应的控制器(子 VC)的替换,表现为当前视图消失和下一视图出现,基于此进行动画,动画的方式非常多,所以限制最终呈现的效果就只有你的想象力了。图中的 Parent VC 可替换为 UIViewController, UITabbarController 或 UINavigationController 中的任何一种。

目前为止,官方支持以下几种方式的自定义转场:

  1. 在 UINavigationController 中 push 和 pop;
  2. 在 UITabBarController 中切换 Tab;
  3. Modal 转场:presentation 和 dismissal,俗称视图控制器的模态显示和消失,仅限于modalPresentationStyle属性为 UIModalPresentationFullScreen 或 UIModalPresentationCustom 这两种模式;
  4. UICollectionViewController 的布局转场:UICollectionViewController 与 UINavigationController 结合的转场方式,实现很简单。

官方的支持包含了 iOS 中的大部分转场方式,还有一种自定义容器中的转场并没有得到系统的直接支持,不过借助协议这种灵活的方式,我们依然能够实现对自定义容器控制器转场的定制,在压轴环节我们将实现这一点。

iOS 7 以协议的方式开放了自定义转场的 API,协议的好处是不再拘泥于具体的某个类,只要是遵守该协议的对象都能参与转场,非常灵活。转场协议由5种协议组成,在实际中只需要我们提供其中的两个或三个便能实现绝大部分的转场动画:

1.转场代理(Transition Delegate):

自定义转场的第一步便是提供转场代理,告诉系统使用我们提供的代理而不是系统的默认代理来执行转场。有如下三种转场代理,对应上面三种类型的转场:

 

这里除了<UIViewControllerTransitioningDelegate>是 iOS 7 新增的协议,其他两种在 iOS 2 里就存在了,在 iOS 7 时扩充了这两种协议来支持自定义转场。

转场发生时,UIKit 将要求转场代理将提供转场动画的核心构件:动画控制器和交互控制器(可选的);由我们实现。

2.动画控制器(Animation Controller):

最重要的部分,负责添加视图以及执行动画;遵守<UIViewControllerAnimatedTransitioning>协议;由我们实现。

3.交互控制器(Interaction Controller):

通过交互手段,通常是手势来驱动动画控制器实现的动画,使得用户能够控制整个过程;遵守<UIViewControllerInteractiveTransitioning>协议;系统已经打包好现成的类供我们使用。

4.转场环境(Transition Context):

提供转场中需要的数据;遵守<UIViewControllerContextTransitioning>协议;由 UIKit 在转场开始前生成并提供给我们提交的动画控制器和交互控制器使用。

5.转场协调器(Transition Coordinator):

可在转场动画发生的同时并行执行其他的动画,其作用与其说协调不如说辅助,主要在 Modal 转场和交互转场取消时使用,其他时候很少用到;遵守<UIViewControllerTransitionCoordinator>协议;由 UIKit 在转场时生成,UIViewController 在 iOS 7 中新增了方法transitionCoordinator返回一个遵守该协议的对象,且该方法只在该控制器处于转场过程中才返回一个此类对象,不参与转场时返回 nil。

总结下,5个协议只需要我们操心3个;实现一个最低限度可用的转场动画,我们只需要提供上面五个组件里的两个:转场代理和动画控制器即可,还有一个转场环境是必需的,不过这由系统提供;当进一步实现交互转场时,还需要我们提供交互控制器,也有现成的类供我们使用。

 

阶段一:非交互转场

这个阶段要做两件事,提供转场代理并由代理提供动画控制器。在转场代理协议里动画控制器和交互控制器都是可选实现的,没有实现或者返回 nil 的话则使用默认的转场效果。动画控制器是表现转场效果的核心部分,代理部分非常简单,我们先搞定动画控制器吧。

 

动画控制器协议

动画控制器负责添加视图以及执行动画,遵守UIViewControllerAnimatedTransitioning协议,该协议要求实现以下方法:

 

最重要的是第一个方法,该方法接受一个遵守<UIViewControllerContextTransitioning>协议的转场环境对象,上一节的 API 解释里提到这个协议,它提供了转场所需要的重要数据:参与转场的视图控制器和转场过程的状态信息。

UIKit 在转场开始前生成遵守转场环境协议<UIViewControllerContextTransitioning>的对象 transitionContext,它有以下几个方法来提供动画控制器需要的信息:

 

通过viewForKey:获取的视图是viewControllerForKey:返回的控制器的根视图,或者 nil。viewForKey:方法返回 nil 只有一种情况: UIModalPresentationCustom 模式下的 Modal 转场 ,通过此方法获取 presentingView 时得到的将是 nil,在后面的 Modal 转场里会详细解释。

前面提到转场的本质是下一个场景的视图替换当前场景的视图,从当前场景过渡下一个场景。下面称即将消失的场景的视图为 fromView,对应的视图控制器为 fromVC,即将出现的视图为 toView,对应的视图控制器称之为 toVC。几种转场方式的转场操作都是可逆的,一种操作里的 fromView 和 toView 在逆向操作里的角色互换成对方,fromVC 和 toVC 也是如此。在动画控制器里,参与转场的视图只有 fromView 和 toView 之分,与转场方式无关。转场动画的最终效果只限制于你的想象力。这也是动画控制器在封装后可以被第三方使用的重要原因。

在 iOS 8 中可通过以下方法来获取参与转场的三个重要视图,在 iOS 7 中则需要通过对应的视图控制器来获取,为避免 API 差异导致代码过长,示例代码中直接使用下面的视图变量:

 

 

动画控制器实现

转场 API 是协议的好处是不限制具体的类,只要对象实现该协议便能参与转场过程,这也带来另外一个好处:封装便于复用,尽管三大转场代理协议的方法不尽相同,但它们返回的动画控制器遵守的是同一个协议,因此可以将动画控制器封装作为第三方动画控制器在其他控制器的转场过程中使用。

三种转场方式都有一对可逆的转场操作,你可以为了每一种操作实现单独的动画控制器,也可以实现通用的动画控制器。处于篇幅的考虑,本文示范一个比较简单的 Slide 动画控制器:Slide left and right,而且该动画控制器在三种转场方式中是通用的,不必修改就可以直接在工程中使用。效果示意图:

 

在交互式转场章节里我们将在这个基础上实现文章开头提到的两种效果:NavigationController 右滑返回 和 TabBarController 滑动切换。尽管对动画控制器来说,转场方式并不重要,可以对 fromView 和 toView 进行任何动画,但上面的动画和 Modal 转场风格上有点不配,主要动画的方向不对,不过我在这个 Slide 动画控制器里为 Modal 转场适配了和系统的风格类似的竖直移动动画效果;另外 Modal 转场并没有比较合乎操作直觉的交互手段,而且和前面两种容器控制器的转场在机制上有些不同,所以我将为 Modal 转场示范另外一个动画。

在转场中操作是可逆的,返回操作时的动画应该也是逆向的。对此,Slide 动画控制器需要针对转场的操作类型对动画的方向进行调整。Swift 中 enum 的关联值可以视作有限数据类型的集合体,在这种场景下极其合适。设定转场类型:

 

使用示例:在 TabBarController 中切换到左边的页面。

let transitionType = SDETransitionType.TabTransition(.Left)

Slide 动画控制器的核心代码:

 

注意上面的代码有2处标记,是动画控制器必须完成的:

  1. 将 toView 添加到容器视图中,使得 toView 在屏幕上显示( Modal 转场中此点稍有不同,下一节细述);
  2. 正确地结束转场过程。转场的结果有两种:完成或取消。非交互转场的结果只有完成一种情况,不过交互式转场需要考虑取消的情况。如何结束取决于转场的进度,通过transitionWasCancelled方法来获取转场的状态,使用completeTransition:来完成或取消转场。

实际上,这里示范的简单的转场动画和那些很复杂的转场动画在转场的部分要做的事情都是上面提到的这两点,它们的区别主要在于动画的部分。

转场结束后,fromView 会从视图结构中移除,UIKit 自动替我们做了这事,你也可以手动处理提前将 fromView 移除,这完全取决于你。UIView的类方法transitionFromView:toView:duration:options:completion:也能做同样的事,使用下面的代码替换上面的代码,甚至不需要获取 containerView 以及手动添加 toView 就能实现一个类似的转场动画:

 

 

特殊的 Modal 转场

Modal 转场的差异

Modal 转场中需要做的事情和两种容器 VC 的转场一样,但在细节上有些差异。

 

UINavigationController 和 UITabBarController 这两个容器 VC 的根视图在屏幕上是不可见的(或者说是透明的),可见的只是内嵌在这两者中的子 VC 中的视图,转场是从子 VC 的视图转换到另外一个子 VC 的视图,其根视图并未参与转场;而 Modal 转场,以 presentation 为例,是从 presentingView 转换到 presentedView,根视图 presentingView 也就是 fromView 参与了转场。而且 NavigationController 和 TabBarController 转场中的 containerView 也并非这两者的根视图。

Modal 转场与两种容器 VC 的转场的另外一个不同是:Modal 转场结束后 presentingView 可能依然可见,UIModalPresentationPageSheet 模式就是这样。这种不同导致了 Modal 转场和容器 VC 的转场对 fromView 的处理差异:容器 VC 的转场结束后 fromView 会被主动移出视图结构,这是可预见的结果,我们也可以在转场结束前手动移除;而 Modal 转场中,presentation 结束后 presentingView(fromView) 并未主动被从视图结构中移除。准确来说,是 UIModalPresentationCustom 这种模式下的 Modal 转场结束时 fromView 并未从视图结构中移除;UIModalPresentationFullScreen 模式的 Modal 转场结束后 fromView 依然主动被从视图结构中移除了。这种差异导致在处理 dismissal 转场的时候很容易出现问题,没有意识到这个不同点的话出错时就会毫无头绪。下面来看看 dismissal 转场时的场景。

ContainerView 在转场期间作为 fromView 和 toView 的父视图。三种转场过程中的 containerView 是 UIView 的私有子类,不过我们并不需要关心 containerView 具体是什么。在 dismissal 转场中:

  1. UIModalPresentationFullScreen 模式:presentation 后,presentingView 被主动移出视图结构,在 dismissal 中 presentingView 是 toView 的角色,其将会重新加入 containerView 中,实际上,我们不主动将其加入,UIKit 也会这么做,前面的两种容器控制器的转场里不是这样处理的,不过这个差异基本没什么影响。
  2. UIModalPresentationCustom 模式:转场时 containerView 并不担任 presentingView 的父视图,后者由 UIKit 另行管理。在 presentation 后,fromView(presentingView) 未被移出视图结构,在 dismissal 中,注意不要像其他转场中那样将 toView(presentingView) 加入 containerView 中,否则本来可见的 presentingView 将会被移除出自身所处的视图结构消失不见。如果你在使用 Custom 模式时没有注意到这点,就很容易掉进这个陷阱而很难察觉问题所在,这个问题曾困扰了我一天。

对于 Custom 模式,我们可以参照其他转场里的处理规则来打理:presentation 转场结束后主动将 fromView(presentingView) 移出它的视图结构,并用一个变量来维护 presentingView 的父视图,以便在 dismissal 转场中恢复;在 dismissal 转场中,presentingView 的角色由原来的 fromView 切换成了 toView,我们再将其重新恢复它原来的视图结构中。测试表明这样做是可行的。但是这样一来,在实现上,需要在转场代理中维护一个动画控制器并且这个动画控制器要维护 presentingView 的父视图,第三方的动画控制器必须为此改造。显然,这样的代价是无法接受的。

小结:经过上面的尝试,建议是,不要干涉官方对 Modal 转场的处理,我们去适应它。在 Custom 模式下,由于 presentingView 不受 containerView 管理,在 dismissal 转场中不要像其他的转场那样将 toView(presentingView) 加入 containerView,否则 presentingView 将消失不见,而应用则也很可能假死;而在 presentation 转场中,切记不要手动将 fromView(presentingView) 移出其父视图。

iOS 8 为<UIViewControllerContextTransitioning>协议添加了viewForKey:方法以方便获取 fromView 和 toView,但是在 Modal 转场里要注意,从上面可以知道,Custom 模式下,presentingView 并不受 containerView 管理,这时通过viewForKey:方法来获取 presentingView 得到的是 nil,必须通过viewControllerForKey:得到 presentingVC 后来获取。因此在 Modal 转场中,较稳妥的方法是从 fromVC 和 toVC 中获取 fromView 和 toView。

顺带一提,前面提到的UIView的类方法transitionFromView:toView:duration:options:completion:能在 Custom 模式下工作,却与 FullScreen 模式有点不兼容。

 

Modal 转场实践

UIKit 已经为 Modal 转场实现了多种效果,当 UIViewController 的modalPresentationStyle属性为.Custom或.FullScreen时,我们就有机会定制转场效果,此时modalTransitionStyle指定的转场动画将会被忽略。

Modal 转场开放自定义功能后最令人感兴趣的是定制 presentedView 的尺寸,下面来我们来实现一个带暗色调背景的小窗口效果。Demo 地址:CustomModalTransition。

 

由于需要保持 presentingView 可见,这里的 Modal 转场应该采用 UIModalPresentationCustom 模式,此时 presentedVC 的modalPresentationStyle属性值应设置为.Custom。而且与容器 VC 的转场的代理由容器 VC 自身的代理提供不同,Modal 转场的代理由 presentedVC 提供。动画控制器的核心代码:

 

 

iOS 8的改进:UIPresentationController

iOS 8 针对分辨率日益分裂的 iOS 设备带来了新的适应性布局方案,以往有些专为在 iPad 上设计的控制器也能在 iPhone 上使用了,一个大变化是在视图控制器的(模态)显示过程,包括转场过程,引入了UIPresentationController类,该类接管了 UIViewController 的显示过程,为其提供转场和视图管理支持。当 UIViewController 的modalPresentationStyle属性为.Custom时(不支持.FullScreen),我们有机会通过控制器的转场代理提供UIPresentationController的子类对 Modal 转场进行进一步的定制。官方对该类参与转场的流程和使用方法有非常详细的说明:Creating Custom Presentations。

UIPresentationController类主要给 Modal 转场带来了以下几点变化:

  1. 定制 presentedView 的外观:设定 presentedView 的尺寸以及在 containerView 中添加自定义视图并为这些视图添加动画;
  2. 可以选择是否移除 presentingView;
  3. 可以在不需要动画控制器的情况下单独工作;
  4. iOS 8 中的适应性布局。

以上变化中第1点 iOS 7 中也能做到,3和4是 iOS 8 带来的新特性,只有第2点才真正解决了 iOS 7 中的痛点。在 iOS 7 中定制外观时,动画控制器需要负责管理额外添加的的视图,UIPresentationController类将该功能剥离了出来独立负责,其提供了如下的方法参与转场,对转场过程实现了更加细致的控制,从命名便可以看出与动画控制器里的animateTransition:的关系:

func presentationTransitionWillBegin func presentationTransitionDidEnd(_ completed: Bool) func dismissalTransitionWillBegin func dismissalTransitionDidEnd(_ completed: Bool)

除了 presentingView,UIPresentationController类拥有转场过程中剩下的角色:

 

没有 presentingView 是因为 Custom 模式下 presentingView 不受 containerView 管理,UIPresentationController类并没有改变这一点。iOS 8 扩充了转场环境协议,可以通过viewForKey:方便获取转场的视图,而该方法在 Modal 转场中获取的是presentedView返回的视图。因此我们可以在子类中将 presentedView 包装在其他视图后重写该方法返回包装后的视图当做 presentedView 在动画控制器中使用。

接下来,我用UIPresentationController子类实现上一节「Modal 转场实践」里的效果,presentingView 和 presentedView 的动画由动画控制器负责,剩下的事情可以交给我们实现的子类来完成。

参与角色都准备好了,但有个问题,无法直接访问动画控制器,不知道转场的持续时间,怎么与转场过程同步?这时候前面提到的用处甚少的转场协调器(Transition Coordinator)将在这里派上用场。该对象可通过 UIViewController 的transitionCoordinator方法获取,这是 iOS 7 为自定义转场新增的 API,该方法只在控制器处于转场过程中才返回一个与当前转场有关的有效对象,其他时候返回 nil。

转场协调器遵守<UIViewControllerTransitionCoordinator>协议,它含有以下几个方法:

//与动画控制器中的转场动画同步,执行其他动画 animateAlongsideTransition:completion: //与动画控制器中的转场动画同步,在指定的视图内执行动画 animateAlongsideTransitionInView:animation:completion:

由于转场协调器的这种特性,动画的同步问题解决了。

 

OverlayPresentationController类接手了 dimmingView 的工作后,需要回到上一节OverlayAnimationController里把涉及 dimmingView 的部分删除,然后在 presentedVC 的转场代理属性transitioningDelegate中提供该类实例就可以实现和上一节同样的效果。

 

在 iOS 7 中,Custom 模式的 Modal 转场里,presentingView 不会被移除,如果我们要移除它并妥善恢复会破坏动画控制器的独立性使得第三方动画控制器无法直接使用;在 iOS 8 中,UIPresentationController解决了这点,给予了我们选择的权力,通过重写下面的方法来决定 presentingView 是否在 presentation 转场结束后被移除:

func shouldRemovePresentersView -> Bool

返回 true 时,presentation 结束后 presentingView 被移除,在 dimissal 结束后 UIKit 会自动将 presentingView 恢复到原来的视图结构中。通过UIPresentationController的参与,Custom 模式完全实现了 FullScreen 模式下的全部特性。

你可能会疑惑,除了解决了 iOS 7中无法干涉 presentingView 这个痛点外,还有什么理由值得我们使用UIPresentationController类?除了能与动画控制器配合,UIPresentationController类也能脱离动画控制器独立工作,在转场代理里我们仅仅提供后者也能对 presentedView 的外观进行定制,缺点是无法控制 presentedView 的转场动画,因为这是动画控制器的职责,这种情况下,presentedView 的转场动画采用的是默认的动画效果,转场协调器实现的动画则是采用默认的动画时间。

iOS 8 带来了适应性布局,<UIContentContainer>协议用于响应视图尺寸变化和屏幕旋转事件,之前用于处理屏幕旋转的方法都被废弃了。UIViewController 和 UIPresentationController 类都遵守该协议,在 Modal 转场中如果提供了后者,则由后者负责前者的尺寸变化和屏幕旋转,最终的布局机会也在后者里。在OverlayPresentationController中重写以下方法来调整视图布局以及应对屏幕旋转:

 

 

转场代理

完成动画控制器后,只需要在转场前设置好转场代理便能实现动画控制器中提供的效果。转场代理的实现很简单,但是在设置代理时有不少陷阱,需要注意。

 

UINavigationControllerDelegate

定制 UINavigationController 这种容器控制器的转场时,很适合实现一个子类,自身集转场代理,动画控制器于一身,也方便使用,不过这样做有时候又限制了它的使用范围,别人也实现了自己的子类时便不能方便使用你的效果,这里采取的是将转场代理封装成一个类。

 

如果你在代码里为你的控制器里这样设置代理:

//错误的做法,delegate 是弱引用,在离开这行代码所处的方法范围后,delegate 将重新变为 nil,然后什么都不会发生。 self.navigationController?.delegate = SDENavigationControllerDelegate

可以使用强引用的变量来引用新实例,且不能使用本地变量,在控制器中新增一个变量来维持新实例就可以了。

self.navigationController?.delegate = strongReferenceDelegate

解决了弱引用的问题,这行代码应该放在哪里执行呢?很多人喜欢在viewDidLoad做一些配置工作,但在这里设置无法保证是有效的,因为这时候控制器可能尚未进入 NavigationController 的控制器栈,self.navigationController返回的可能是 nil;如果是通过代码 push 其他控制器,在 push 前设置即可;prepareForSegue:sender:方法是转场前更改设置的最后一次机会,可以在这里设置;保险点,使用UINavigationController子类,自己作为代理,省去到处设置的麻烦。

不过,通过代码设置终究显得很繁琐且不安全,在 storyboard 里设置一劳永逸:在控件库里拖拽一个 NSObject 对象到相关的 UINavigationControler 上,在控制面板里将其类别设置为SDENavigationControllerDelegate,然后拖拽鼠标将其设置为代理。

最后一步,像往常一样触发转场:

self.navigationController?.pushViewController(toVC, animated: true)//or self.navigationController?.popViewControllerAnimated(true)

在 storyboard 中通过设置 segue 时开启动画也将看到同样的 Slide 动画。Demo 地址:NavigationControllerTransition。

 

UITabBarControllerDelegate

同样作为容器控制器,UITabBarController 的转场代理和 UINavigationController 类似,通过类似的方法提供动画控制器,不过<UINavigationControllerDelegate>的代理方法里提供了操作类型,但<UITabBarControllerDelegate>的代理方法没有提供滑动的方向信息,需要我们来获取滑动的方向。

 

为 UITabBarController 设置代理的方法和陷阱与上面的 UINavigationController 类似,注意delegate属性的弱引用问题。点击 TabBar 的相邻页面进行切换时,将会看到 Slide 动画;通过以下代码触发转场时也将看到同样的效果:

tabBarVC.selectedIndex = ...//or tabBarVC.selectedViewController = ...

 

UIViewControllerTransitioningDelegate

Modal 转场的代理协议<UIViewControllerTransitioningDelegate>是 iOS 7 新增的,其为 presentation 和 dismissal 转场分别提供了动画控制器。在「特殊的 Modal 转场」里实现的OverlayAnimationController类可同时处理 presentation 和 dismissal 转场。UIPresentationController只在 iOS 8中可用,通过available关键字可以解决 API 的版本差异。

 

Modal 转场的代理由 presentedVC 的transitioningDelegate属性来提供,这与前两种容器控制器的转场不一样,不过该属性作为代理同样是弱引用,记得和前面一样需要有强引用的变量来维护该代理,而 Modal 转场需要 presentedVC 来提供转场代理的特性使得 presentedVC 自身非常适合作为自己的转场代理。另外,需要将 presentedVC 的modalPresentationStyle属性设置为.Custom或.FullScreen,只有这两种模式下才支持自定义转场,该属性默认值为.FullScreen。自定义转场时,决定转场动画效果的modalTransitionStyle属性将被忽略。

开启转场动画的方式依然是两种:在 storyboard 里设置 segue 并开启动画,但这里并不支持.Custom模式,不过还有机会挽救,转场前的最后一个环节prepareForSegue:sender:方法里可以动态修改modalPresentationStyle属性;或者全部在代码里设置,示例如下:

 

 

阶段二:交互式转场

激动人心的部分来了,好消息是交互转场的实现难度比你想象的要低。

 

实现交互化

在非交互转场的基础上将之交互化需要两个条件:

  1. 由转场代理提供交互控制器,这是一个遵守<UIViewControllerInteractiveTransitioning>协议的对象,不过系统已经打包好了现成的类UIPercentDrivenInteractiveTransition供我们使用。我们不需要做任何配置,仅仅在转场代理的相应方法中提供一个该类实例便能工作。另外交互控制器必须有动画控制器才能工作。
  2. 交互控制器还需要交互手段的配合,最常见的是使用手势,或是其他事件,来驱动整个转场进程。

满足以上两个条件很简单,但是很容易犯错误。

正确地提供交互控制器

如果在转场代理中提供了交互控制器,而转场发生时并没有方法来驱动转场进程(比如手势),转场过程将一直处于开始阶段无法结束,应用界面也会失去响应:在 NavigationController 中点击 NavigationBar 也能实现 pop 返回操作,但此时没有了交互手段的支持,转场过程卡壳;在 TabBarController 的代理里提供交互控制器存在同样的问题,点击 TabBar 切换页面时也没有实现交互控制。因此仅在确实处于交互状态时才提供交互控制器,可以使用一个变量来标记交互状态,该变量由交互手势来更新状态。

以为 NavigationController 提供交互控制器为例:

 

TabBarController 的实现类似,Modal 转场代理分别为 presentation 和 dismissal 提供了各自的交互控制器,也需要注意上面的问题。

问题的根源是交互控制的工作机制导致的,交互过程实际上是由转场环境对象<UIViewControllerContextTransitioning>来管理的,它提供了如下几个方法来控制转场的进度:

 

// http://toutiao.com/i6261731102088495618/?tt_from=mobile_qq&utm_campaign=client_share&app=news_article&utm_source=mobile_qq&iid=3817009924&utm_medium=toutiao_ios