View Controller Transition(转场)

转场的理解

转场过程中,作为容器的父 VC (UIViewController、UITabbarController 或 UINavigationController) 维护着多个子 VC,但在视图结构上,只保留一个子 VC 的视图。

转场的本质是下一场景(子 VC)的视图替换当前场景(子 VC)的视图以及相应的控制器(子 VC)的替换,表现为当前视图消失和下一视图出现,基于此进行动画。

具体可以看下图:

iOS中几种转场方式

Apple支持如下几种方式的自定义转场,并且提供转场代理来实现自定义转场。

  1. UINavigationController 中 push 和 pop

    1
    UINavigationController 的 delegate 属性需遵守 <UINavigationControllerDelegate>
  2. UITabBarController 中切换 Tab

    1
    UITabBarController 的 delegate 属性需遵守 <UITabBarControllerDelegate>
  3. Modal 转场:presentation 和 dismissal,俗称视图控制器的模态显示和消失,仅限于 vc.modalPresentationStyle = .custom 或者 .fullScreen 时才能定制转场效果。

    1
    vc.transitioningDelegate 遵守 <UIViewControllerTransitioningDelegate>
  4. UICollectionViewController 的布局转场:仅限于 UICollectionViewController 与 UINavigationController 结合的转场方式

如何实现自定义 Transition(转场)

1、自定义 Transition Delegate (转场代理) 提供给 控制器

2、在转场代理中提供转场动画的核心构件: 动画控制器 或者 交互控制器

  • 动画控制器(Animation Controller):负责添加视图以及执行动画,遵守 UIViewControllerAnimatedTransitioning 协议

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

3、在动画控制器、交互控制器中,根据转场环境(Transition Context)提供的数据,进行转场操作。

  • 转场环境(Transition Context):遵守 UIViewControllerContextTransitioning 协议,由UIKit在转场开始前生成,为我们提供转场所需要的数据。

4、可以为转场代理提供 呈现控制器(UIPresentationController), 用于 UIViewController 的显示过程,为其提供转场和视图管理支持。

  • 转场协调器(Transition Coordinator):遵守 UIViewControllerTransitionCoordinator 协议,为 UIPresentationController 中的动画的与转场进行同步,主要在 Modal 转场和交互转场取消时使用。
1
2
3
4
5
6
7
8
// 获取转场协调器
viewController.transitionCoordinator

// 转场协调器的方法
//与动画控制器中的转场动画同步,执行其他动画
func animate(alongsideTransition:, completion: ) -> Bool
//与动画控制器中的转场动画同步,在指定的视图内执行动画
func animateAlongsideTransition(in: , animation: , completion: ) -> Bool

一般来说,(push/presentation)转场结束后,UIKit 会把 fromView 从视图结构中移除,你也可以手动处理提前将 fromView 移除;(pop/dismissal)转场结束后,UIKit 会自动将 toView(presentingView) 加入 containerView。

iOS中三大转场代理中的方法不尽相同,但是它们返回的 动画控制器 和 交互控制器 所遵守的协议相同,这样便于我们封装。

Modal转场呈现的差别

从图中可以看出,Custom 模式下Modal转场后,fromeView 和 toView 在视图层次中还是存在可见的,在不同的 UITransitionView 中;而 Navigtion 转场结束后,fromeVC会被主动移出视图,只显示 toVC。

容器类 VC 的转场里 fromView 和 toView 是 containerView 的子层次的视图,而 Modal 转场里 presentingView 与 containerView 是同层次的视图,只有 presentedView 是 containerView 的子层次视图。

UITransitionView == containerView

它是UIView 的私有子类,用来呈现 presentedView.

可以在转场环境(Transition Context)中获取到它:

1
let containerView = transitionContext.containerView

或者 UIPresentationController 中的 containerView;

UIPresentationController

UIPresentationController是 iOS 8.0中新增的,它接管了 UIViewController 的显示过程,为其提供转场和视图管理支持。

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

当 vc.modalPresentationStyle = .fullScreen 时

presentation 结束后 即将消失的场景的视图 presentingView(fromView) 会主动被UIKit从视图结构中移除,presentingView(fromView)为屏幕的 bounds;

dismissal 转场时,如果需要对 presentingView(fromView) 进行动画,则需要手动将其加入 containerView 并添加动画;转场结束后,UIKit 会自动将其恢复到原来的位置。

当 vc.modalPresentationStyle = .custom 时

presentation 结束后,presentingView(fromView) 不会被主动移出视图结构,与 即将出现的视图 presentedView(toView) 同时显示。

dismissal 结束后本来可见的 presentingView 将会随着 containerView 一起被移除,此时只显示 presentedView(toView)。

交互式转场

实现交互化的条件

  • 给转场代理提供 交互控制器(遵守 UIViewControllerInteractiveTransitioning 协议),一般使用系统提供的 UIPercentDrivenInteractiveTransition 类。

  • 交互控制器 需要 动画控制器交互方式 配合才能使用。最常见的 交互方式 是使用手势,或是其他事件,来驱动整个转场进程。

转场过程卡壳

如果给转场代理提供了 交互控制器,而发生转场并不是通过 交互方式 来驱动转场进程,比如点击返回按钮进行 push/pop 转场,这样会导致转场过程将一直处于开始阶段无法结束,应用界面也会失去响应,转场过程卡壳。

因此,仅在确实处于交互状态时才提供交互控制器。

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

1
2
3
func updateInteractiveTransition(_ percentComplete: CGFloat)
func finishInteractiveTransition()
func cancelInteractiveTransition()

在转场代理里提供了交互控制器后,转场开始时,该方法自动被 UIKit 调用对转场环境进行配置。

系统打包好的UIPercentDrivenInteractiveTransition中的控制转场进度的方法与转场环境对象提供的三个方法同名,实际上只是前者调用了后者的方法而已。

交互转场介入后, view 的 Appear 状态变得复杂

交互转场介入后,view 的Will系方法和Did系方法的执行顺序并不能得到保证

1
2
3
4
func viewWillAppear(_ animated: Bool)
func viewDidAppear(_ animated: Bool)
func viewWillDisappear(_ animated: Bool)
func viewDidDisappear(_ animated: Bool)

那么,如何在转场过程中的任意阶段中断时取消不需要的效果?

学习的demo

1、iOS-ViewController-Transition:《iOS 视图控制器转场详解》配套 Demo,代码很全

2、Navigation Transition:简单的 push/pop 手势转场,博客中提到一些其它转场动画

3、CustomTransitions:Apple 提供的转场讲解代码,包含三种常用的转场

4、ShrinkingModalTransitions:Modal转场,类似ActionSheet,详细见其博客 使用 UIPercentDrivenInteractiveTransition 的讲解。

5、AnimationTrasitionDemo:点击 UITableCell 进行转场放大 cell 图片,详细见其博客 哈哈大p孩 讲解

开源库

TransitionTreasury:swift,2.1k

Hero:swift,18.5k,转场动画

Spring:swift,13.7k,简化Swift中的iOS动画

学习博客

github/seedante iOS 视图控制器转场详解
cnthinkcode
[MFMAL’s Blog](http://mfmal.tech/2016/08/10/使用`presentViewController`方法弹出模态控制器 并进行自定义操作(一))
ObjC 中国
iOS自定义过渡动画

Apple View Controller Programming Guide for iOS
Apple Creating Custom Presentations
Apple Customizing the Transition Animations

文章作者: Czm
文章链接: http://yoursite.com/2020/06/21/View-Controller-Transition-%E8%BD%AC%E5%9C%BA/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Czm