歷時5天從各種英文教程中學(xué)習(xí)到的過渡動畫夺克,是一個很難忘的探索經(jīng)歷
比較好的參考文章自定義UIViewController過渡入門 怠堪,動畫入門。
轉(zhuǎn)場方式
首先讓我們來了解iOS轉(zhuǎn)場的方式:
- UINavigationController push/pop UIViewController導(dǎo)航欄的轉(zhuǎn)場
- UITabBarController 切換Tab的轉(zhuǎn)場
- present/dismiss 模態(tài)的方式轉(zhuǎn)場
這是iOS提供的3種基本轉(zhuǎn)場方式骑祟,默認(rèn)的轉(zhuǎn)場方式轉(zhuǎn)場風(fēng)格有限。例如模態(tài)轉(zhuǎn)場中,盡管有modalPresentationStyle
和modalTransitionStyle
關(guān)于展現(xiàn)風(fēng)格和過渡風(fēng)格的設(shè)置得哆,但是轉(zhuǎn)場仍是死板從底部彈出脯颜。這并不能滿足我們在軟件開發(fā)的需求,側(cè)邊欄贩据、頂部欄的動畫效果都無法很好地實現(xiàn)栋操。在iOS 7.0之后Apple提供一套完整的自定義過渡動畫的API,為各種轉(zhuǎn)場動畫的實現(xiàn)帶了無限可能饱亮。
這里主要介紹模態(tài)轉(zhuǎn)場的自定義動畫矾芙。
頂部欄的動畫效果:
Modal 轉(zhuǎn)場
模態(tài)轉(zhuǎn)場分為非交互式轉(zhuǎn)場和交互式轉(zhuǎn)場。
非交互式轉(zhuǎn)場也就是普通轉(zhuǎn)場近上,轉(zhuǎn)場的動畫無法交互剔宪,不能在動畫的過程中終止轉(zhuǎn)場。
交互式轉(zhuǎn)場能通過手指觸摸屏幕通過滑動體驗過渡動畫的進行壹无,并能終止動畫過程葱绒。
過渡動畫API
我們定義:如果視圖控制器Apresent
之后展示視圖控制器B。在后文中斗锭,源視圖控制器為fromVC地淀,目標(biāo)視圖控制器為toVC。
狀態(tài) | 視圖控制器A | 視圖控制器B |
---|---|---|
Present | 源視圖控制器 | 目標(biāo)視圖控制器 |
Dissmiss | 目標(biāo)視圖控制器 | 源視圖控制器 |
transitioningDelegate 過渡代理
每個視圖控制器UIViewController都有一個transitioningDelegate屬性岖是,該代理需遵循UIViewControllerTransitioningDelegate
協(xié)議帮毁,提供相關(guān)動畫控制器实苞。
每當(dāng)您顯示或關(guān)閉視圖控制器時,UIKit都會要求其過渡代理使用動畫控制器烈疚。要將默認(rèn)動畫替換為您自己的自定義動畫黔牵,必須實現(xiàn)過渡代理,并使其返回適當(dāng)?shù)膭赢嬁刂破鳌?/p>
AnimationController 動畫控制器
過渡代理在present/dismiss時返回相應(yīng)的動畫控制器胞得。動畫控制器是過渡動畫的核心荧止。它完成了動畫過渡的“繁重工作”。
TransitioningContext 過渡語境
過渡語境在過渡過程中實現(xiàn)并起著至關(guān)重要的作用:它封裝了有關(guān)過渡中涉及的視圖和視圖控制器的信息阶剑。過渡語境輔助動畫控制器實現(xiàn)動畫跃巡。
從圖中可以看出,自己并沒有實現(xiàn)此協(xié)議牧愁。UIKit會為您創(chuàng)建和配置過渡上下文素邪,并在每次發(fā)生過渡時將其傳遞給動畫控制器。
非交互式過渡動畫的過程
以present過渡動畫為例:
- 通過代碼或
StoryBoard segue
觸發(fā)模態(tài)視圖present過程猪半。 - UIKit向
toVC
(目標(biāo)視圖控制器)請求其過渡代理兔朦。如果沒有,UIKIt將使用標(biāo)準(zhǔn)的內(nèi)置過渡磨确。 - 然后沽甥,UIKit通過來向過渡代理請求動畫控制器
animationController(forPresented:presenting:source:)
。如果返回nil
乏奥,則過渡將使用默認(rèn)動畫摆舟。 - UIKit構(gòu)造過渡語境。UIKit通過調(diào)用向動畫控制器詢問動畫的持續(xù)時間
transitionDuration(using:)
邓了。UIKitanimateTransition(using:)
在動畫控制器上調(diào)用以執(zhí)行過渡的動畫恨诱。 - 最后,動畫控制器調(diào)用
completeTransition(_:)
過渡上下文以指示動畫已完成骗炉。
dimiss過渡動畫的步驟幾乎相同照宝。
在這種情況下,UIKit向fromVC
視圖控制器(正在關(guān)閉的視圖控制器)請求其過渡代理句葵,要求提供動畫控制器animationController(forDismissed:)
厕鹃。
非交互式過渡動畫需要提供的條件
- 設(shè)置過渡動畫代理。設(shè)置(目標(biāo)視圖控制器)的
transitioningDelegate
屬性乍丈,即設(shè)置過渡動畫代理對象熊响,該代理對象遵循UIViewControllerTransitioningDelegate
協(xié)議,實現(xiàn)forPresented
和forDismissed
兩個方法诗赌,分別提供present和dismiss的視圖控制器實例汗茄。 - 創(chuàng)建動畫控制器。創(chuàng)建present和dismiss的動畫控制器铭若,實現(xiàn)動畫持續(xù)時間和構(gòu)造動畫方法洪碳。
這里我們完成一個簡單的從左到右的過渡動畫递览。
1. 構(gòu)建present/dimiss場景
這里不再講述構(gòu)建過程,無論是stroyboard還是純代碼都是很好完成的瞳腌。這里為了方便绞铃,使用storyboard完成。
左視圖控制器為:ViewController
右視圖控制器為:LeftViewController
2. 創(chuàng)建Animation Controller
Animation Controller是自定義過渡動畫的核心對象嫂侍。
AnimationControlle繼承至NSObject
儿捧,遵循UIViewControllerAnimatedTransitioning
協(xié)議,實現(xiàn)兩個required方法挑宠。
class AnimationController: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
//要求提供動畫師對象的動畫持續(xù)時間屬性
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
//過渡動畫的實現(xiàn)效果菲盾,也是自定義過渡動畫的核心方法,需要構(gòu)建動畫的實現(xiàn)各淀。
}
}
這里貼出Present狀態(tài)下的AnimationController的代碼懒鉴,Dismiss狀態(tài)與其類似(動畫過程執(zhí)行反過程)。
import UIKit
class PresentAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.6
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// 1
guard let _ = transitionContext.viewController(forKey: .from),
let toVC = transitionContext.viewController(forKey: .to) else {
return
}
// 2
let containerView = transitionContext.containerView
containerView.addSubview(toVC.view)
let duration = transitionDuration(using: transitionContext)
toVC.view.frame = CGRect(x: -ScreenWidth, y: 0, width: ScreenWidth, height: ScrennHeight)
// 3
UIView.animate(withDuration: duration, animations: {
toVC.view.frame = CGRect(x: 0, y: 0, width: ScreenWidth, height: ScrennHeight)
}) { (_) in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
在第一個transitionDuration(using:)
方法中設(shè)置動畫的持續(xù)時間碎浇。
在第二個transitionDuration(using:)
方法中構(gòu)建動畫临谱。
- 獲取過渡動畫所需的視圖控制器及snapshot。從過渡語境中
transitionContext.viewController
我們可以獲取源視圖控制器fromVC和目標(biāo)視圖控制器toVC奴璃,過渡語境封裝了設(shè)計的視圖控制器的信息悉默,極大地幫助我們處理視圖控制器的動畫轉(zhuǎn)化。還可以獲取fromVC和toVC的snapshot(屏幕快照)苟穆,來構(gòu)造更加復(fù)雜和優(yōu)秀的動畫抄课。 - 管理過渡語境的容器視圖 --- containerView和視圖動畫的位置初始化。UIKit將整個過渡封裝在容器視圖中鞭缭,以簡化視圖層次結(jié)構(gòu)和動畫的管理,容器視圖負(fù)責(zé)管理
fromVC.view
和toVC.view
魏颓。由UIKit創(chuàng)建的容器視圖僅包含fromVC視圖岭辣。您必須添加任何其他將參與過渡的視圖。
addSubview(_:)將新視圖置于視圖層次結(jié)構(gòu)中的所有其他視圖之前甸饱,因此添加子視圖的順序很重要沦童。
- 設(shè)置動畫效果。動畫有兩種實現(xiàn)方法叹话,一種是基礎(chǔ)動畫
animate
偷遗,另一種是關(guān)鍵幀動畫animateKeyframes
。這里只是簡單地將fromVC.view
從屏幕的左邊界外移動到屏幕中驼壶。<font color = red>注意:在動畫完成后需要調(diào)用completeTransition(_:)
通知UIKit動畫已完成氏豌。這將確保最終狀態(tài)是一致的。</font>
3.設(shè)置過渡動畫代理
設(shè)置(目標(biāo)視圖控制器)destinationVC的modalPresentationStyle
為枚舉屬性custom热凹,過渡動畫的代理為self即(源視圖控制器)ViewController泵喘。
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationVC = segue.destination as? LeftViewController {
destinationVC.modalPresentationStyle = .custom
destinationVC.transitioningDelegate = self
}
}
然后在(源視圖控制器)添加擴展泪电,遵循UIViewControllerTransitioningDelegate
協(xié)議,實現(xiàn)forPresented
和forDismissed
方法纪铺。
extension ViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
//目標(biāo)VC是presented相速,源VC是presenting
guard let _ = presented as? LeftViewController, let _ = presenting as? ViewController else {
return nil
}
return PresentAnimationController()
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
guard let _ = dismissed as? LeftViewController else {
return nil
}
return DismissAnimationController()
}
}
這樣就構(gòu)建好了一個簡單、基礎(chǔ)的自定義轉(zhuǎn)場過渡動畫鲜锚。
交互式過渡動畫
交互式動畫會使使用者的動畫體驗更自然舒適而不顯得尖銳突诬,給控制動畫過程留下了余地。iOS原生APP設(shè)置中便有這樣的交互式動畫的例子芜繁。
過渡動畫的進度跟隨手指的滑動來啟動/終止轉(zhuǎn)場動畫旺隙,這樣可以帶來良好的用戶交互體驗。
交互式控制器的工作方式
交互控制器通過加快浆洗,減慢甚至反轉(zhuǎn)過渡的過程來響應(yīng)觸摸事件或編程輸入催束。為了啟用交互式轉(zhuǎn)換,轉(zhuǎn)換代理必須提供一個交互控制器伏社。您已經(jīng)制作了過渡動畫抠刺。在過渡動畫的基礎(chǔ)上,Apple將交互式動畫封裝成交互控制器將響應(yīng)手勢來管理此動畫摘昌,而不是讓其像視頻一樣播放速妖。Apple提供了現(xiàn)成的UIPercentDrivenInteractiveTransition
類,通過繼承該類來創(chuàng)建自己的交互式控制器聪黎。
1. 創(chuàng)建交互式控制器
我們在之前過渡動畫的基礎(chǔ)上構(gòu)建交互式過渡動畫罕容,我們首先需要創(chuàng)建交互式控制器。這里貼出顯示交互式控制器PresentInteractionController
的代碼稿饰,繼承封裝好的UIPercentDrivenInteractiveTransition
類锦秒。
class PresentInteractionController: UIPercentDrivenInteractiveTransition {
var interactionInProgress = false
private var shouldCompleteTrantision = false
private weak var viewController: UIViewController!
private weak var toViewController: UIViewController!
init(viewController: UIViewController, toViewController: UIViewController) {
super.init()
self.viewController = viewController
self.toViewController = toViewController
prepareGestureRecognizer(in: self.viewController.view)
}
private func prepareGestureRecognizer(in view: UIView) {
let gesture = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleGsture(_:)))
gesture.edges = .left
view.addGestureRecognizer(gesture)
}
@objc func handleGsture(_ gestureRecognizer: UIScreenEdgePanGestureRecognizer) {
let translation = gestureRecognizer.translation(in: gestureRecognizer.view?.superview)
var progress = translation.x / 200
progress = CGFloat(fminf(fmaxf(Float(progress), 0.0), 1.0))
switch gestureRecognizer.state {
case .began:
interactionInProgress = true
viewController.present(toViewController, animated: true, completion: nil)
case .changed:
shouldCompleteTrantision = progress > 0.5
update(progress)
case .cancelled:
interactionInProgress = false
cancel()
case .ended:
interactionInProgress = false
if shouldCompleteTrantision {
finish()
} else {
cancel()
}
default:
break
}
}
}
-
interactionInProgress
Bool屬性,表示交互式場景是否在發(fā)生喉镰。 -
shouldCompleteTrantision
Bool屬性旅择,表示是否應(yīng)該終止過渡動畫。用于內(nèi)部管理過渡侣姆。 -
viewController
和toViewController
生真,獲取源視圖控制器和目標(biāo)視圖控制器的引用,用于管理過渡某狀態(tài)present視圖控制器捺宗,達到交互式控制器與動畫控制器相聯(lián)系的作用柱蟀。 -
prepareGestureRecognizer(in:)
為源視圖添加屏幕手勢的方法,這里的交互式動畫為從左往右present出VC蚜厉,所以為源視圖添加屏幕相應(yīng)在.left
的手勢长已。 -
handleGsture(_:)
為相應(yīng)手勢變化從而改變過渡動畫狀態(tài)的方法。通過聲明局部變量以跟蹤滑動進度translation
,根據(jù)translation
在視圖中獲取并計算過渡進度progress
痰哨。手勢開始時胶果,您將設(shè)置interactionInProgress
為true
并觸發(fā)prsent
視圖控制器。手勢移動時斤斧,調(diào)用update(_:)
更新過渡進度早抠。這是一種根據(jù)UIPercentDrivenInteractiveTransition
您傳入的百分比移動過渡的方法。如果取消手勢撬讽,則更新interactionInProgress
并取消過渡蕊连。一旦動作已經(jīng)結(jié)束,您使用的過渡進度來決定cancel()
或finish()
游昼。
2. 控制器聯(lián)系
(源)視圖控制器與交互控制器相聯(lián)
在viewController
中添加以下屬性:
var presentInteractionController: PresentInteractionController?
并在viewDidLoad()
方法中初始化屬性:
presentInteractionController = PresentInteractionController(viewController: self, toViewController: leftViewController)
交互控制器與動畫控制器相聯(lián)
在PresentAnimationController
中添加以下屬性:
let interactionController: PresentInteractionController?
并添加init
方法:
init(interactionController: PresentInteractionController?) {
self.interactionController = interactionController
}
3. 實現(xiàn)過渡代理協(xié)議方法
動畫控制器forPresented
方法中修改PresentAnimationController
對象的創(chuàng)建甘苍。
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
//展現(xiàn)VC是presented,源VC是presenting
guard let _ = presented as? LeftViewController, let fromVC = presenting as? ViewController else {
return nil
}
return PresentAnimationController(interactionController: fromVC.presentInteractionController)
}
添加以下方法:
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
guard let animator = animator as? PresentAnimationController,
let interactionController = animator.interactionController,
interactionController.interactionInProgress else {
return nil
}
return interactionController
}
這首先檢查所涉及的動畫控制器是否為PresentAnimationController
烘豌。如果是這樣载庭,它將獲取關(guān)聯(lián)的交互控制器的引用,并驗證用戶交互正在進行中廊佩。如果不滿足這些條件中的任何一個囚聚,它將返回nil
以便轉(zhuǎn)換將繼續(xù)進行而不會發(fā)生交互。否則标锄,它將交互控制器交還給UIKit顽铸,以便它可以管理過渡。
dismiss
狀態(tài)的交互式控制器的方法也是相近的料皇,最終實現(xiàn)效果如下:
以上便是自定義過渡動畫的全部內(nèi)容谓松,讀者學(xué)習(xí)完后可以學(xué)一些進階動畫,參考一些優(yōu)秀App的轉(zhuǎn)場動畫践剂,進而創(chuàng)建自己過渡動畫鬼譬,從而獲得良好的用戶體驗。