iOS ViewContrller转场动画学习笔记
本文只用来记录学习的modal方式的转场,一般需要实现的动画包括:presentation,dismissal两种可能实现的动画.
FromView 和 ToView
在代码中,经常使用FromView
和ToView
.FromView
表示当前的视图view,ToView
表示要跳转的视图View.例如,presentation中,Avc present 出 Bvc,则A的view视图就是FromView
,而B的view就是ToView
.相反,dismissal中,从Bvc dismiss 到Avc,此时Bvc的视图view就是FromView
,A的视图view就是ToView
.
Presenting和presented
这组概念也很重要,容易和上面的FromView
,ToView
混淆.如果是Avc present Bvc,那么A就是Presenting,B就是presented.不论当时的动作是Presentation还是Dismissal.UIKit中讲A称为presentingViewController
,B称为presentedViewController
.通过主动或者被动很容易区分出来.
modalPrentationStyle
viewController中关于modal相关的属性: FullScreen
和Custom
两种.区别在与FullScreen
时候在containerView中会主动移除fromView
,而Custom
模式中,containerView不会主动移除fromView
.
这个属性的特点非常重要.如果需要present方式时候想看到presentingViewController的view,那么这里一定要设置成
Custom
.
自定义转场动画
最简单的转场动画需要如下步骤:
- 创建一个遵守
UIViewControllerAnimatedTransitioning
协议的对象 - 实现该协议的两个重要的方法
//指定转场动画持续的时长
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval
//转场动画的具体内容
func animateTransition(transitionContext: UIViewControllerContextTransitioning)
- 使得presentingViewController或者创建一个类遵守
UIViewControllerTransitioningDelegate
协议,并且实现如下两个方法,返回一个前面创建的满足Animation协议的对象
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
- 设置presentedViewController的transitioningDelegate属性
上面步骤中,所有的动画都在func animateTransition(transitionContext:)
中完成,这里需要发挥你的想象力.具体的FromView
到ToView
的动画如何展现.
特殊的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 转场中:
- UIModalPresentationFullScreen 模式:presentation 后,presentingView 被主动移出视图结构,在 dismissal 中 presentingView 是 toView 的角色,其将会重新加入 containerView 中,实际上,我们不主动将其加入,UIKit 也会这么做,前面的两种容器控制器的转场里不是这样处理的,不过这个差异基本没什么影响。
- UIModalPresentationCustom 模式:转场时 containerView 并不担任 presentingView 的父视图,后者由 UIKit 另行管理。在 presentation 后,fromView(presentingView) 未被移出视图结构,在 dismissal 中,注意不要像其他转场中那样将 toView(presentingView) 加入 containerView 中,否则本来可见的 presentingView 将会被移除出自身所处的视图结构消失不见。如果你在使用 Custom 模式时没有注意到这点,就很容易掉进这个陷阱而很难察觉问题所在,这个问题曾困扰了我一天。
小结:建议是,不要干涉官方对 Modal 转场的处理,我们去适应它。在 Custom 模式下,由于 presentingView 不受 containerView 管理,在 dismissal 转场中不要像其他的转场那样将 toView(presentingView) 加入 containerView,否则 presentingView 将消失不见,而应用则也很可能假死;在 presentation 转场中,切记不要手动将 fromView(presentingView) 移出其父视图。
iOS 8 为协议添加了viewForKey:方法以方便获取 fromView 和 toView,但是在 Modal 转场里要注意,从上面可以知道,Custom 模式下,presentingView 并不受 containerView 管理,这时通过viewForKey:方法来获取 presentingView 得到的是 nil,必须通过viewControllerForKey:得到 presentingVC 后来获取。因此在 Modal 转场中,较稳妥的方法是从 fromVC 和 toVC 中获取 fromView 和 toView。
总结要点
- 一般来说,Modal 转场的delegate由 presentedVC 提供
- 在presentation/push时候,要调用
containerView.addSubView(toView)
,toView加入到containerView是每个转场必须完成的一步.modal中的Custom模式下的Dismiss 里不要将 toView添加到containerView,因为在present的时候,系统就没有将它从containerView中删除. - 在UIView.animation的completion中要调用如下代码:
//2
let isCancelled = transitionContext.transitionWasCancelled()
transitionContext.completeTransition(!isCancelled)
- Modal 转场一般需要 presentedVC 来提供转场
UIViewControllerTransitioningDelegate
的代理. modal方式中自定义转场时,决定转场动画效果的modalTransitionStyle属性将被忽略.具体动画由代理中返回的animationController决定
参考文献
https://github.com/wazrx/XWTrasitionPractice
http://www.jianshu.com/p/45434f73019e
http://www.cocoachina.com/ios/20160309/15605.html
偶然遇到的crash:
1 怎么连续dismiss两个viewController?
直接[self.presentingViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];就可以了,控制器堆栈是dismiss掉下面的,上面的自动就dismiss了.或者直接将动画设置成No2 连续push或者present会crash - 因为动画的问题.如果有此类需求.直接关闭动画