WWDC之CustomTransitionsUsingViewControllers

前言

iOS7之后,Apple在自定义转场动画方面上新增了许多API,让开发者更加快捷高效地自定义视图控制器间pop/push,present/dismiss,或者tabbarcontroller子控制器间的切换.而之后也正是基于这些API,让许多App有了许多incredible转场动画效果.该Session发布于2013年整体介绍了关于新增转场动画相关的API和以及如何利用这些API基本实现原理和注意,以下为所涉及的内容:

  • 新动画相关API
  • 自定义视图控制器转场
  • 交互式视图控制器转场
  • 转场过程的控制处理

内容

新动画相关API

基础动画API
iOS5时Apple就增加了许多与基于Block的UIView动画API.众所皆知,其UIView动画API本质是对CoreAnimation API的高级封装,满足了大多数简单动画效果的实现.

BasicBlock动画.png

如上图所示,在UIView动画的Block内更新视图的可动画属性,本质会转换为在该视图的layer上进行相应属性更新,创建相应CAnimation动画对象,并添加到该layer上,最后我们就能看到block内的视图的动画效果了.
现在针对视图属性的变化但不想产生动画效果,甚至是在Block动画API中,也禁止产生动画,UIView有了新的Block API:
performWithoutAnimation(actionsWithoutAnimation: () -> Void)

        //动画过程:直接进行performWithoutAnimation内部Block的视图属性更新,且无动画效果;
        //再具体执行animateWithDuration内的视图动画.
        UIView.animateWithDuration(1.0) { () -> Void in                       
            self.subView.alpha = 0.5;
            UIView.performWithoutAnimation({ () -> Void in
                self.subView.center = self.view.center
            })
        }

Spring Animation
为了创建更加有活力的,具有物理弹性效果的动画,引入了基于Block的Spring动画API(对CASpringAnimation动画API高级封装,CASpringAnimation在iOS9才开放给开发者):
animateWithDuration(duration:delay:dampingRatio:velocity:options:animations:completion:

  • dampingRatio 表示阻尼比例,取值0~1,阻尼比例越小,振荡幅度越大.
  • velocity 用于计算Spring动画开始时动画元素的初速度,一秒内所位移距离与velocity相乘获得初速度,例如在1s时间内,Spring动画的总距离是200point,你想要动画的开始,以匹配一个视图开始速度为100 point/s(200 point * 0.5 / 1 s),设置velocity值为0.5即可。
//Spring动画示例

        UIView.animateWithDuration(1.0, 
                    delay: 0.0, 
   usingSpringWithDamping: 0.3, 
    initialSpringVelocity: 0.5,
                  options: .OverrideInheritedOptions, 
               animations: { () -> Void in
            
            self.subView.center = self.view.center
            
            }, completion: nil)

Key-frame 动画API
如同UIView的基本动画BlcokAPI是对CABasicAnimation的高级封装,基于Block的帧动画API就是对于CAKeyframeAnimation的UIKit层面的封装:

public class func animateKeyframesWithDuration(duration: NSTimeInterval, delay: NSTimeInterval, options: UIViewKeyframeAnimationOptions, animations: () -> Void, completion: ((Bool) -> Void)?)

public class func addKeyframeWithRelativeStartTime(frameStartTime: Double, relativeDuration frameDuration: Double, animations: () -> Void)

针对每一帧的动画参数设置在整体的Block内使用
addKeyframeWithRelativeStartTime..方法进行配置,其中frameStartTimeframeDuration 取值都在0~1之间,都是相对整体duration的比例值,根据frameStartTime得到相对于整体动画时间的时间点,表示在整个动画duration中该帧动画执行时机;而根据frameDuration得到相对于整体动画时间的持续时间,表示该帧将执行动画时持续的时间,但超过整体动画duration的剩余时间时,系统会按照其剩余时间作为帧动画持续时间使用.

// 示例
        UIView.animateKeyframesWithDuration(4.0,
            delay: 0.0,
            options: .AllowUserInteraction,
            animations: { () -> Void in
                
                //Frame 1  after 2s(4*0.5) start,duration: 0.8s(4*0.2), remain: 1.2s(4-2-0.8)
                UIView.addKeyframeWithRelativeStartTime(0.5,
                    relativeDuration: 0.2,
                    animations: { () -> Void in
                        self.subView.center = CGPointMake(self.view.center.x, 0)
                })
                
                // Frame 2 after 2.8s(2.0+0.8 <=> 0.7*4) start,duration: 1.2s(0.3*4 <=> 4-2.8), remain: 0.0s
                UIView.addKeyframeWithRelativeStartTime(0.7,
                    relativeDuration: 0.3,
                    animations: { () -> Void in
                        self.subView.center = self.view.center
                })
            },
            completion: nil)

Snapshot API
为了能更方便使用动画元素,增加了UIView关于快照生成的API,所生成的快照视图可以使用在转场动画中.

        view.snapshotViewAfterScreenUpdates(Bool)
        view.resizableSnapshotViewFromRect(CGRect, afterScreenUpdates: Bool, withCapInsets: UIEdgeInsets)

其中第二个方法可以更加自由地选择快照区域.

UIDynamic API
关于UIDynamic相关动画API由于有专门Session进行演示和说明,因此没有进行详细的介绍,只是提到用于辅助视图产生多种动态,更接近物理变化效果的动画API,在自定义控制器转场动画有许多高级的用法.以后会针对UIDynamic相关Session进行记录.

自定义视图控制器转场

设置自定义的转场

  • Presention and dismissals(针对模态视图的转场)
    1. 设置ModalPresentationStyle,自定义时赋值为FullScreen或Custom(区别:赋值Custom时FromViewController(来源控制器)的视图不会从当前窗口层次中移除,仍然存在.)
    2. 设置from控制器的transitioningDelegate
    3. 调用presentViewController/dismissViewController
  • Tabbarcontroller的子控制器切换转场
    1. 设置tabBarControllerDelegate代理对象
    2. 调用setSelectedIndex/setSelectedController
  • NavigationControler的push/pop转场
    1. 设置navigationControllerDelegate代理对象
    2. 调用pushVieController/popViewController
  • push/pop下CollectionController布局切换时的转场
    1. 设置需要定义转场效果的collectionController的useLayoutToLayoutNavigationTransition属性为true
    2. 调用pushVieController/popViewController

Concepts & API

  • 所有转场动画效果都将在遵守UIViewControllerContextTransitioning协议的context下进行,你可以通过context的系列方法如viewControllerForKey/viewForKey获得from/to相关的视图控制器和视图,也可以通过containerView获得转场过渡的容器视图,转场相关的视图都必须加入到该视图层次中,并且根据转场需要来设置两者层次顺序,最后转场动画执行结束后为了让视图和控制器层次一致而必须要调用completeTransition(Bool)方法,通知系统转场是否完成.
  • 而转场动画相关context,只能在遵守UIViewControllerAnimatedTransitioning协议方法中返回,因此无论自定义哪种转场都必须返回各自的实现UIViewControllerAnimatedTransitioning协议方法的自定义转场对象.
  • Apple给出了常规情况下针对控制器间视图转场所涉及的代理对象
    • 针对Tabbar视图: UITabBarControllerDelegate (新增了方法)
    • 针对导航视图: UINavigationControllerDelegate (新增了方法)
    • 针对模态视图: UIViewControllerTransitioningDelegate (新增该类)
      只要赋值其各自的代理对象,并在其代理方法中返回自定义转场对象,就可以实现非交互式的转场动画.
      (若要使转场动画可交互则允许交互的控制器的代理对象中除此之外,还要再返回一个实现了UIViewControllerInteractiveTransitioning协议方法的交互式转场对象)

交互式视图控制器转场

  • 现在应用程序中最常见控制器间交互式转场的就是pop/push时基于手势驱动的交互转场了.
  • 现在自定义交互式转场也不一定需要手势驱动,自定义其他驱动方法如:摆放位置,加速度变化...;并且交互式转场必须可以被开始和被停止.
  • UIKit提供了一个交互控制器类UIPercentDrivenInteractiveTransition,让开发者能以此继承,来简单配置百分比驱动的交互转场动画.而使用上,首先转场动画必须在UIView的BlockAPI中进行,其次提供一个继承于UIPercentDrivenInteractiveTransition的实例中,最后在逻辑代码中调用其update/cancel/finish相关方法,start方法由系统内部调用.

Note: 交互式转场对象的使用,必须在自定义转场对象存在的前提下使用,只有已经实现了返回自定义转场对象的方法,系统才会查询是否存在交互式转场对象进行使用.

Coordinator与转场过程的控制

在交互式动画转场过程中,由于其交互性而系统则时刻监听着转场的进行状态:

  • start状态
  • update状态
  • end 状态
  • complete状态
  • cancel状态

在这些状态的变化中,实际上代表转场间两种的视图控制器的视图生命周期变化,如图:

Transition State Change.png

这里重点提到了由于交互转场中cancel状态的出现,会使得视图控制器的viewDidAppear方法不一定会在viewWillAppear后调用,之后而是调用viewWillDisappear,对于viewWillDisappear来说也如此,之后不一定会去调用viewDidDisappear.
因此为了当交互转场被取消时仍能要调用viewDiDAppear方法中的转场完成后的清理处理代码,Apple引入了UIViewControllerTransitionCoordinator协议对象transitionCoordinator,通常让其在veiwWillAppear中利用notifyWhenInteractionEndsUsingBlock(handler:)方法在交互状态为取消时,执行原本在viewDiDAppear方法中的转场完成后的清理处理的代码,来完成这次交互转场的取消.

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        
        let transitionCoordinator = self.transitionCoordinator()
        transitionCoordinator?.notifyWhenInteractionEndsUsingBlock({ (ctx) -> Void in
            if ctx.isCancelled() {
                // do some clean code in viewDidAppear
            }
        })
    }

无论哪种转场方式,其控制器都会有个transitionCoordinator(利用UIViewController的transitionCoordinator()获得),也可以利用它来执行转场完成后的handle回调;使用协议的animateAlongsideTransition..系列方法,还可以执行出转场动画之外的其他动画,让两种动画是同时进行的.

        //导航控制器push后利用transitionCoordinator的方法执行completion handle.
        self.navigationController?.pushViewController(self, animated: true)
        let transitionCoordinator =  self.navigationController?.transitionCoordinator()
        transitionCoordinator?.animateAlongsideTransition(nil, completion: { (ctx) -> Void in
            // do completion handle
        })

总结

初看这个Session,一系列超长命名的协议以及协议方法有点让人眼花缭乱,还有点晕,但通过一点点回放细听,并且加上思考理解,看完这个Session对转场中视图生命周期,转场过程,以及转场过程中重要阶段有了未曾深入的了解,也对于转场的整体把握和注意点有了大致的了解和体会,收获不小O(∩_∩)O哈!

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

推荐阅读更多精彩内容

  • 前言的前言 唐巧前辈在微信公众号「iOSDevTips」以及其博客上推送了我的文章后,我的 Github 各项指标...
    VincentHK阅读 5,349评论 3 44
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,066评论 4 62
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,813评论 25 707
  • 前段时间看了些关于RxJava的文章和代码,感觉虽然讲的很透彻,奈何理解的比较少,只会用一些最基础的方法。囧 昨天...
    Echo剑心阅读 1,724评论 15 28
  • 前几天通过一个非法的手段在网上看完了《记忆大师》这部电影。至于为什么是非法的手段,是因为这部电影目前在各大平台都是...
    十六村阅读 450评论 2 0