前言
iOS7之后,Apple在自定義轉(zhuǎn)場動(dòng)畫方面上新增了許多API,讓開發(fā)者更加快捷高效地自定義視圖控制器間pop/push,present/dismiss,或者tabbarcontroller子控制器間的切換.而之后也正是基于這些API,讓許多App有了許多incredible轉(zhuǎn)場動(dòng)畫效果.該Session發(fā)布于2013年整體介紹了關(guān)于新增轉(zhuǎn)場動(dòng)畫相關(guān)的API和以及如何利用這些API基本實(shí)現(xiàn)原理和注意,以下為所涉及的內(nèi)容:
- 新動(dòng)畫相關(guān)API
- 自定義視圖控制器轉(zhuǎn)場
- 交互式視圖控制器轉(zhuǎn)場
- 轉(zhuǎn)場過程的控制處理
內(nèi)容
新動(dòng)畫相關(guān)API
基礎(chǔ)動(dòng)畫API
iOS5時(shí)Apple就增加了許多與基于Block的UIView動(dòng)畫API.眾所皆知,其UIView動(dòng)畫API本質(zhì)是對CoreAnimation API的高級封裝,滿足了大多數(shù)簡單動(dòng)畫效果的實(shí)現(xiàn).

如上圖所示,在UIView動(dòng)畫的Block內(nèi)更新視圖的可動(dòng)畫屬性,本質(zhì)會(huì)轉(zhuǎn)換為在該視圖的layer上進(jìn)行相應(yīng)屬性更新,創(chuàng)建相應(yīng)CAnimation動(dòng)畫對象,并添加到該layer上,最后我們就能看到block內(nèi)的視圖的動(dòng)畫效果了.
現(xiàn)在針對視圖屬性的變化但不想產(chǎn)生動(dòng)畫效果,甚至是在Block動(dòng)畫API中,也禁止產(chǎn)生動(dòng)畫,UIView有了新的Block API:
performWithoutAnimation(actionsWithoutAnimation: () -> Void)
//動(dòng)畫過程:直接進(jìn)行performWithoutAnimation內(nèi)部Block的視圖屬性更新,且無動(dòng)畫效果;
//再具體執(zhí)行animateWithDuration內(nèi)的視圖動(dòng)畫.
UIView.animateWithDuration(1.0) { () -> Void in
self.subView.alpha = 0.5;
UIView.performWithoutAnimation({ () -> Void in
self.subView.center = self.view.center
})
}
Spring Animation
為了創(chuàng)建更加有活力的,具有物理彈性效果的動(dòng)畫,引入了基于Block的Spring動(dòng)畫API(對CASpringAnimation動(dòng)畫API高級封裝,CASpringAnimation在iOS9才開放給開發(fā)者):
animateWithDuration(duration:delay:dampingRatio:velocity:options:animations:completion:
- dampingRatio 表示阻尼比例,取值0~1,阻尼比例越小,振蕩幅度越大.
- velocity 用于計(jì)算Spring動(dòng)畫開始時(shí)動(dòng)畫元素的初速度,一秒內(nèi)所位移距離與velocity相乘獲得初速度,例如在1s時(shí)間內(nèi),Spring動(dòng)畫的總距離是200point,你想要?jiǎng)赢嫷拈_始,以匹配一個(gè)視圖開始速度為100 point/s(200 point * 0.5 / 1 s),設(shè)置velocity值為0.5即可。
//Spring動(dòng)畫示例
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 動(dòng)畫API
如同UIView的基本動(dòng)畫BlcokAPI是對CABasicAnimation的高級封裝,基于Block的幀動(dòng)畫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)
針對每一幀的動(dòng)畫參數(shù)設(shè)置在整體的Block內(nèi)使用
addKeyframeWithRelativeStartTime..方法進(jìn)行配置,其中frameStartTime和frameDuration 取值都在0~1之間,都是相對整體duration的比例值,根據(jù)frameStartTime得到相對于整體動(dòng)畫時(shí)間的時(shí)間點(diǎn),表示在整個(gè)動(dòng)畫duration中該幀動(dòng)畫執(zhí)行時(shí)機(jī);而根據(jù)frameDuration得到相對于整體動(dòng)畫時(shí)間的持續(xù)時(shí)間,表示該幀將執(zhí)行動(dòng)畫時(shí)持續(xù)的時(shí)間,但超過整體動(dòng)畫duration的剩余時(shí)間時(shí),系統(tǒng)會(huì)按照其剩余時(shí)間作為幀動(dòng)畫持續(xù)時(shí)間使用.
// 示例
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
為了能更方便使用動(dòng)畫元素,增加了UIView關(guān)于快照生成的API,所生成的快照視圖可以使用在轉(zhuǎn)場動(dòng)畫中.
view.snapshotViewAfterScreenUpdates(Bool)
view.resizableSnapshotViewFromRect(CGRect, afterScreenUpdates: Bool, withCapInsets: UIEdgeInsets)
其中第二個(gè)方法可以更加自由地選擇快照區(qū)域.
UIDynamic API
關(guān)于UIDynamic相關(guān)動(dòng)畫API由于有專門Session進(jìn)行演示和說明,因此沒有進(jìn)行詳細(xì)的介紹,只是提到用于輔助視圖產(chǎn)生多種動(dòng)態(tài),更接近物理變化效果的動(dòng)畫API,在自定義控制器轉(zhuǎn)場動(dòng)畫有許多高級的用法.以后會(huì)針對UIDynamic相關(guān)Session進(jìn)行記錄.
自定義視圖控制器轉(zhuǎn)場
設(shè)置自定義的轉(zhuǎn)場
- Presention and dismissals(針對模態(tài)視圖的轉(zhuǎn)場)
- 設(shè)置ModalPresentationStyle,自定義時(shí)賦值為FullScreen或Custom(區(qū)別:賦值Custom時(shí)FromViewController(來源控制器)的視圖不會(huì)從當(dāng)前窗口層次中移除,仍然存在.)
- 設(shè)置from控制器的transitioningDelegate
- 調(diào)用presentViewController/dismissViewController
- Tabbarcontroller的子控制器切換轉(zhuǎn)場
- 設(shè)置tabBarControllerDelegate代理對象
- 調(diào)用setSelectedIndex/setSelectedController
- NavigationControler的push/pop轉(zhuǎn)場
- 設(shè)置navigationControllerDelegate代理對象
- 調(diào)用pushVieController/popViewController
- push/pop下CollectionController布局切換時(shí)的轉(zhuǎn)場
- 設(shè)置需要定義轉(zhuǎn)場效果的collectionController的useLayoutToLayoutNavigationTransition屬性為true
- 調(diào)用pushVieController/popViewController
Concepts & API
- 所有轉(zhuǎn)場動(dòng)畫效果都將在遵守
UIViewControllerContextTransitioning協(xié)議的context下進(jìn)行,你可以通過context的系列方法如viewControllerForKey/viewForKey獲得from/to相關(guān)的視圖控制器和視圖,也可以通過containerView獲得轉(zhuǎn)場過渡的容器視圖,轉(zhuǎn)場相關(guān)的視圖都必須加入到該視圖層次中,并且根據(jù)轉(zhuǎn)場需要來設(shè)置兩者層次順序,最后轉(zhuǎn)場動(dòng)畫執(zhí)行結(jié)束后為了讓視圖和控制器層次一致而必須要調(diào)用completeTransition(Bool)方法,通知系統(tǒng)轉(zhuǎn)場是否完成. - 而轉(zhuǎn)場動(dòng)畫相關(guān)context,只能在遵守
UIViewControllerAnimatedTransitioning協(xié)議方法中返回,因此無論自定義哪種轉(zhuǎn)場都必須返回各自的實(shí)現(xiàn)UIViewControllerAnimatedTransitioning協(xié)議方法的自定義轉(zhuǎn)場對象. - Apple給出了常規(guī)情況下針對控制器間視圖轉(zhuǎn)場所涉及的代理對象
- 針對Tabbar視圖: UITabBarControllerDelegate (新增了方法)
- 針對導(dǎo)航視圖: UINavigationControllerDelegate (新增了方法)
- 針對模態(tài)視圖: UIViewControllerTransitioningDelegate (新增該類)
只要賦值其各自的代理對象,并在其代理方法中返回自定義轉(zhuǎn)場對象,就可以實(shí)現(xiàn)非交互式的轉(zhuǎn)場動(dòng)畫.
(若要使轉(zhuǎn)場動(dòng)畫可交互則允許交互的控制器的代理對象中除此之外,還要再返回一個(gè)實(shí)現(xiàn)了UIViewControllerInteractiveTransitioning協(xié)議方法的交互式轉(zhuǎn)場對象)
交互式視圖控制器轉(zhuǎn)場
- 現(xiàn)在應(yīng)用程序中最常見控制器間交互式轉(zhuǎn)場的就是pop/push時(shí)基于手勢驅(qū)動(dòng)的交互轉(zhuǎn)場了.
- 現(xiàn)在自定義交互式轉(zhuǎn)場也不一定需要手勢驅(qū)動(dòng),自定義其他驅(qū)動(dòng)方法如:擺放位置,加速度變化...;并且交互式轉(zhuǎn)場必須可以被開始和被停止.
- UIKit提供了一個(gè)交互控制器類
UIPercentDrivenInteractiveTransition,讓開發(fā)者能以此繼承,來簡單配置百分比驅(qū)動(dòng)的交互轉(zhuǎn)場動(dòng)畫.而使用上,首先轉(zhuǎn)場動(dòng)畫必須在UIView的BlockAPI中進(jìn)行,其次提供一個(gè)繼承于UIPercentDrivenInteractiveTransition的實(shí)例中,最后在邏輯代碼中調(diào)用其update/cancel/finish相關(guān)方法,start方法由系統(tǒng)內(nèi)部調(diào)用.
Note: 交互式轉(zhuǎn)場對象的使用,必須在自定義轉(zhuǎn)場對象存在的前提下使用,只有已經(jīng)實(shí)現(xiàn)了返回自定義轉(zhuǎn)場對象的方法,系統(tǒng)才會(huì)查詢是否存在交互式轉(zhuǎn)場對象進(jìn)行使用.
Coordinator與轉(zhuǎn)場過程的控制
在交互式動(dòng)畫轉(zhuǎn)場過程中,由于其交互性而系統(tǒng)則時(shí)刻監(jiān)聽著轉(zhuǎn)場的進(jìn)行狀態(tài):
- start狀態(tài)
- update狀態(tài)
- end 狀態(tài)
- complete狀態(tài)
- cancel狀態(tài)
在這些狀態(tài)的變化中,實(shí)際上代表轉(zhuǎn)場間兩種的視圖控制器的視圖生命周期變化,如圖:

這里重點(diǎn)提到了由于交互轉(zhuǎn)場中cancel狀態(tài)的出現(xiàn),會(huì)使得視圖控制器的
viewDidAppear方法不一定會(huì)在viewWillAppear后調(diào)用,之后而是調(diào)用viewWillDisappear,對于viewWillDisappear來說也如此,之后不一定會(huì)去調(diào)用viewDidDisappear.因此為了當(dāng)交互轉(zhuǎn)場被取消時(shí)仍能要調(diào)用
viewDiDAppear方法中的轉(zhuǎn)場完成后的清理處理代碼,Apple引入了UIViewControllerTransitionCoordinator協(xié)議對象transitionCoordinator,通常讓其在veiwWillAppear中利用notifyWhenInteractionEndsUsingBlock(handler:)方法在交互狀態(tài)為取消時(shí),執(zhí)行原本在viewDiDAppear方法中的轉(zhuǎn)場完成后的清理處理的代碼,來完成這次交互轉(zhuǎn)場的取消.
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
}
})
}
無論哪種轉(zhuǎn)場方式,其控制器都會(huì)有個(gè)transitionCoordinator(利用UIViewController的transitionCoordinator()獲得),也可以利用它來執(zhí)行轉(zhuǎn)場完成后的handle回調(diào);使用協(xié)議的animateAlongsideTransition..系列方法,還可以執(zhí)行出轉(zhuǎn)場動(dòng)畫之外的其他動(dòng)畫,讓兩種動(dòng)畫是同時(shí)進(jìn)行的.
//導(dǎo)航控制器push后利用transitionCoordinator的方法執(zhí)行completion handle.
self.navigationController?.pushViewController(self, animated: true)
let transitionCoordinator = self.navigationController?.transitionCoordinator()
transitionCoordinator?.animateAlongsideTransition(nil, completion: { (ctx) -> Void in
// do completion handle
})
總結(jié)
初看這個(gè)Session,一系列超長命名的協(xié)議以及協(xié)議方法有點(diǎn)讓人眼花繚亂,還有點(diǎn)暈,但通過一點(diǎn)點(diǎn)回放細(xì)聽,并且加上思考理解,看完這個(gè)Session對轉(zhuǎn)場中視圖生命周期,轉(zhuǎn)場過程,以及轉(zhuǎn)場過程中重要階段有了未曾深入的了解,也對于轉(zhuǎn)場的整體把握和注意點(diǎn)有了大致的了解和體會(huì),收獲不小O(∩_∩)O哈!