iOS 中自定义转场指的是 view controller 之间的切换,分为两种类型:
- model presentation
- navigation push/pop
系统为这两种类型都提供了默认的实现,对于 model presentation
来说,还可以通过指定 modalTransitionStyle
来达到不同的转场效果。
当然,我们也可以通过实现一系列的 protocol
来自定义转场。
- UIViewControllerContextTransitioning,transition context 对象,顾名思义,就是用来记录上下文信息的,由系统创建并传递给 animator 和 interaction controller。
- UIViewControllerAnimatedTransitioning,一般把实现该接口的对象称为 animator,主要用来完成转场动画。
- UIViewControllerInteractiveTransitioning,一般把实现该接口的对象称为 interaction controller,主要用来完成交互式转场。
- UIViewControllerTransitioningDelegate,用于指定 view controller 转场时需要用到的 animator 或者 interaction controller等。
对于普通的动画转场来说,需要分为两种情况,搞混这两种情况很容易触发一些奇怪的 bug,并且在低版本的 iOS 系统上很可能会失效。
在 iOS 8 之前,sdk 只为我们提供了 animator 的方式来实现转场动画,需要两步:
- 实现 UIViewControllerTransitioningDelegate,给要被 present 的 VC 的 transitioningDelegate 赋值。
- 实现 UIViewControllerAnimatedTransitioning,在
animateTransition:
中对 toView 和 fromView 做动画。
这里有几个值得注意的问题:
第一,首先要搞清楚 modalPresentationStyle
这个值,默认是 FullScreen,也就是全屏转场,全屏转场有其特殊性,在 presenting 过后,fromView 会被移除,所以如果你想在这个模式下做一些非全屏的转场,换句话说 toView 不会覆盖住整个屏幕,那么你会在 presenting 过后得到一个黑色的背景,解决办法是在 finish 过后将 fromView 添加到 toView 之下。
第二,无论是在 present 还是 dismissal 过程中,fromView
已经被自动添加到 containerView 上,而 toView
始终都是需要你手动添加到 containerView 上去的,两个过程中 toView
和 fromView
是互换的,所以对于不同过程,两个 view 的初始状态和结束状态需要理清楚。
第三,无论是在官方文档还是在其他博文中,普遍的做法是借助一个标志量 presenting 来区分两个过程,为的是复用代码,但是经过我实践发现,这样做很容易把两个过程搞混淆,我建议最好使用两个不同的 animator。
为了实现非全屏即自定义的转场,从 iOS 8 开始,sdk 为我们提供了一个 UIPresentationController 的类来封装 modal presentation 的逻辑,这个类的出现,一定程度上是为了简化自定义转场中屏幕尺寸的适应问题。
使用这个类来自定义转场,需要做到三步:
- 设置 presented view controller 的 modalPresentationStyle 为 custom。
- 实现 UIViewControllerAnimatedTransitioning 中的
presentationControllerForPresentedViewController
方法,并且给 presented view controller 的 transitionDelegate 赋值。 - 自定义 UIPresenttationController 的子类,重写里面的方法,对 presented view 做一些自定义。
- 实现 UIViewControllerAnimatedTransitioning,在
animateTransition:
中对 toView 和 fromView 做动画。
custom 与 fullScreen 不同的是在 presenting 过后不会移除 fromView,所以在 dismissal 的过程中 toView 就不用我们手动添加了。
交互式转场相当于给动画转场锦上添花,所以实现交互转场的前提是要先实现动画转场。苹果已经为我们做好了封装一个 UIPercentDrivenInteractiveTransition
,我们只需要做的是,给需要手势驱动的过程的 view 加上手势,然后在回调函数中更新动画进度。对于 present 来说,在手势开始时,需要调用 present:animated
方法,对于 dismissal 来说,则需要调用 dismiss:completion
方法。这个过程可以封装在 UIPercentDrivenInteractiveTransition
的子类中,也可以直接写在 view controller 中。
通过查看 api 可以看到,在 UINavigationControllerDelegate 中提供了和 UIViewControllerTransitioningDelegate 类似的方法,在这里指定了 animator 和 interaction controller 后就和之前的没什么区别了。