iOS 自定义转场动画

简介

在日常开发中动画是必不可少的,苹果也为iOS开发提供了很多好的动画效果,作为iOS开发者自然需要对动画有所了解。在这些动画中,有一种动画是用于一个场景转换到另一个场景的过渡动画,我们称之为转场动画,本文主要内容是关于转场动画的。

转场,顾名思义是场景的转换,即界面由一个场景转换到另一个场景。在iOS中可以分为视图控制器转换视图的转换两个层次,本文的主要结构如下:

  • 转场动画简介
  • 视图控制器转场的实现机制 -- 五大协议
  • 视图转场的实现 -- CATransition

视图控制器转场 -- View Controller Transition

在iOS 7之前,系统已经提供了一些默认的视图控制器转场动画,但是这些动画是完全由系统实现的,不能进行自定义。在iOS 7的时候,系统开放了部分API,使得自定义转场动画成为现实。

自定义转场动画相关的API主要包括五个协议,下面分别介绍下:

  • 转场代理
  • 转场上下文环境协议
  • 动画控制器协议
  • 交互控制器协议
  • 转场协调器协议

1、转场代理

视图控制器中的视图显示在屏幕上有两种方式:1、内嵌在容器中,例如UINavigationController、UITabBarController、 2、模态弹出,即Present/dismiss

对于这些方式,系统在对应的代理协议中都提供了关于转场动画的相关方法,下面逐个分析下:

1.1 导航栏代理 - UINavigationControllerDelegate

optional func navigationController(_ navigationController: UINavigationController, 
            animationControllerFor operation: UINavigationController.Operation, 
                              from fromVC: UIViewController, 
                                to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?

该方法返回一个遵守UIViewControllerAnimatedTransitioning协议的可选对象,该协议即为动画控制器协议,在后文会专门介绍,这里先做个简介。我们可以在遵守该协议的类中进行转场动画的设计,如果返回的对象为nil,则保持系统动画,不会使用自定义动画。

参数中,operation是一个枚举类型,其case为 .none、.push、.pop;fromVC表示push/pop动作发生时显示的ViewController,即动画之前的ViewController,toVC表示动画结束后的ViewController,例如push动作由 A -> B,则fromVC为A,toVC为B,当发生pop动作 B -> A时,fromVC为B,toVC为A。

optional func navigationController(_ navigationController: UINavigationController, 
          interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?

该方法返回一个遵守UIViewControllerInteractiveTransitioning协议的可选对象,该协议对象为一个可交互的转场对象,即交互控制协议对象,该对象定义了转场动画的交互行为。

1.2 模态的代理 - UIViewControllerTransitioningDelegate

optional func animationController(forPresented presented: UIViewController, 
                       presenting: UIViewController, 
                           source: UIViewController) -> UIViewControllerAnimatedTransitioning?

optional func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?

这两个方法均返回一个遵守UIViewControllerAnimatedTransitioning协议的可选对象,用于在模态弹出时,返回自定义转场动画的对象。与导航栏有所不同的是,模态弹出分为了 present 和 dismiss 两个方法。

在present方法中,presented表示被present的ViewController,source表示调用方法的ViewController,presenting可以与source相同,也可以不相同,当source作为一个childViewController时,presenting为source的父控制器,否则presenting与source相同

optional func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?

optional func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?

与设置动画控制器的方法一样,在设置交互控制器时,模态方式也分为了 present 和 dismiss 两个方法,分别用来设置present 和 dismiss时的交互行为。

1.3 tabbar的转场代理

optional func tabBarController(_ tabBarController: UITabBarController, 
animationControllerForTransitionFrom fromVC: UIViewController, 
                            to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?

optional func tabBarController(_ tabBarController: UITabBarController, 
      interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?

与上文所述一致,这两个方法分别返回动画控制器和交互控制器。

2、转场上下文

转场上下文是一个遵守UIViewControllerContextTransitioning协议的对象,该对象由系统自动创建,无需我们进行管理。UIViewControllerContextTransitioning协议包含如下API:

@available(iOS 2.0, *) var containerView: UIView { get }

containerView是一个容器,转场动画前后的View以及要添加的动画视图都是添加在这个容器中的。事实上,该容器的类型是UIViewControllerWrapperView,无论是自定义转场,还是系统定义的转场,最终都是添加在该view上,并且作为一个全局的上下文,该view只存在一份,因此需要合理管理其子视图。

@available(iOS 2.0, *) func viewController(forKey key: UITransitionContextViewControllerKey) -> UIViewController?

根据UITransitionContextViewControllerKeyfromto获取转场前后的ViewController,并且与上文所述fromVC和toVC一致,转场前后的ViewController是可逆的。例如push时 A -> B,则A是 fromVC, B是 toVC;在pop回去是二者的位置则发生了对调。

@available(iOS 8.0, *) func view(forKey key: UITransitionContextViewKey) -> UIView?

在iOS 7及之前,我们只能通过获取到转场前后的ViewController,进而获取其view来获取转场前后的视图,但是在iOS 8及以后,我们可以直接获取转场前后的View。

var transitionWasCancelled: Bool { get }

该属性为一个只读的计算属性,表示转场是否被取消。当转场动画为一个可交互式动画时,动画进行过程中可以手动触发取消,如果取消了则该属性为true,而如果没有取消,则为false,对于一个非交互式动画,则该值一直为false。

func completeTransition(_ didComplete: Bool)

当转场动画完成或者被取消时,调用该方法。

3、动画控制器

动画控制器协议定义了一系列API,用以配置实现转场动画。我们创建一个自己的类,并遵守该协议,然后通过转场代理将自定义动画的Trasnsition返回给系统。

动画控制器中有两个主要的API,分别为:

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval

该方法返回一个时长,在该方法中定义转场动画持续的时间

func animateTransition(using transitionContext: UIViewControllerContextTransitioning)

在该方法中,定义转场要做的动画。

4、交互控制器

交互控制器协议是用来配置转场动画的交互事件的,实际的动画依然由动画控制器来完成。系统定义了UIViewControllerInteractiveTransitioning协议,但是在实际使用时,我们不会直接遵守该协议,而是继承UIPercentDrivenInteractiveTransition类。

UIPercentDrivenInteractiveTransition是系统提供的类,根据苹果官方文档的解释,创建一个交互式动画对象最简单的方式就是继承该类,如下图所示。

该类的API主要有如下几个:

open var duration: CGFloat { get } // 动画时长,根据transitionDuration:的返回值确定

open var percentComplete: CGFloat { get }  // 完成百分比

open func update(_ percentComplete: CGFloat)  // 更新动画完成百分比

open func cancel()  // 取消动画

open func finish()  // 完成动画

// 上诉三个方法对应UIViewControllerContextTransitioning协议中的 updateInteractiveTransition、cancelInteractiveTransition()、finishInteractiveTransition()方法

5、转场协调器

转场协调器用于帮助做一些辅助动画,由系统进行创建,我们通过UIViewController的分类中的属性即可获取,代码如图:

[图片上传失败...(image-9eed14-1648431789762)]

在UIViewControllerTransitionCoordinator中定义了如下API

func animate(alongsideTransition animation: ((UIViewControllerTransitionCoordinatorContext) -> Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Void)? = nil) -> Bool

在该方法中添加动画,当转场动画执行完毕后,会继续执行animation闭包中的动画,而如果使用UIView.animate(withDuration的方式添加动画,则会在转场动画执行时,动画就已经执行完毕。

CATransition

上文介绍了ViewController间的转场实现机制,在CoreAnimation框架中,还有一个动画类用来做layer级的转场,即CATransition。CATransition是作用于CALayer上的,因此需要将CATransition添加到view的layer属性上。

CATransition是继承自CAAnimation的类,其自己所包含的属性由:

open var type: CATransitionType
image.png

该属性表示转场的类型,例如 fade、push、moveIn等,具体效果可参考CATransitionDemo[1]

open var subtype: CATransitionSubtype?复制代码
image.png

该属性表示转场的方向,但是对于fade这种与方向无关的转场,该属性是没有效果的。

/* The amount of progress through to the transition at which to begin
     * and end execution. Legal values are numbers in the range [0,1].
     * `endProgress' must be greater than or equal to `startProgress'.
     * Default values are 0 and 1 respectively. */

open var startProgress: Float

open var endProgress: Float

这两个属性分别表示开始时的进度和结束时的进度,结合父类的duration属性,可以控制动画的开始和结束为止。需要注意的是,这两个属性的值为[0, 1],并且 startProgress要小于 endProgress。

总结

本文主要介绍了转场动画的实现流程,主要有如下两部分内容:

  • 1、ViewController的转场

  • 2、View的转场

分别对应转场的五大协议CATransition。本文只是介绍了相关的API使用,具体Demo可参照网上的CATransitionDemo[2]和VCTransitionsLibrary[3]

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容