歷時5天從各種英文教程中學習到的過渡動畫,是一個很難忘的探索經歷
比較好的參考文章自定義UIViewController過渡入門 ,動畫入門。
轉場方式
首先讓我們來了解iOS轉場的方式:
- UINavigationController push/pop UIViewController導航欄的轉場
- UITabBarController 切換Tab的轉場
- present/dismiss 模態的方式轉場
這是iOS提供的3種基本轉場方式,默認的轉場方式轉場風格有限。例如模態轉場中,儘管有modalPresentationStyle
和modalTransitionStyle
關於展現風格和過渡風格的設置,但是轉場仍是死板從底部彈出。這並不能滿足我們在軟件開發的需求,側邊欄、頂部欄的動畫效果都無法很好地實現。在iOS 7.0之後Apple提供一套完整的自定義過渡動畫的API,爲各種轉場動畫的實現帶了無限可能。
這裏主要介紹模態轉場的自定義動畫。
頂部欄的動畫效果:
Modal 轉場
模態轉場分爲非交互式轉場和交互式轉場。
非交互式轉場也就是普通轉場,轉場的動畫無法交互,不能在動畫的過程中終止轉場。
交互式轉場能通過手指觸摸屏幕通過滑動體驗過渡動畫的進行,並能終止動畫過程。
過渡動畫API
我們定義:如果視圖控制器Apresent
之後展示視圖控制器B。在後文中,源視圖控制器爲fromVC,目標視圖控制器爲toVC。
狀態 | 視圖控制器A | 視圖控制器B |
---|---|---|
Present | 源視圖控制器 | 目標視圖控制器 |
Dissmiss | 目標視圖控制器 | 源視圖控制器 |
transitioningDelegate 過渡代理
每個視圖控制器UIViewController都有一個transitioningDelegate屬性,該代理需遵循UIViewControllerTransitioningDelegate
協議,提供相關動畫控制器。
每當您顯示或關閉視圖控制器時,UIKit都會要求其過渡代理使用動畫控制器。要將默認動畫替換爲您自己的自定義動畫,必須實現過渡代理,並使其返回適當的動畫控制器。
AnimationController 動畫控制器
過渡代理在present/dismiss時返回相應的動畫控制器。動畫控制器是過渡動畫的核心。它完成了動畫過渡的“繁重工作”。
TransitioningContext 過渡語境
過渡語境在過渡過程中實現並起着至關重要的作用:它封裝了有關過渡中涉及的視圖和視圖控制器的信息。過渡語境輔助動畫控制器實現動畫。
從圖中可以看出,自己並沒有實現此協議。UIKit會爲您創建和配置過渡上下文,並在每次發生過渡時將其傳遞給動畫控制器。
非交互式過渡動畫的過程
以present過渡動畫爲例:
- 通過代碼或
StoryBoard segue
觸發模態視圖present過程。 - UIKit向
toVC
(目標視圖控制器)請求其過渡代理。如果沒有,UIKIt將使用標準的內置過渡。 - 然後,UIKit通過來向過渡代理請求動畫控制器
animationController(forPresented:presenting:source:)
。如果返回nil
,則過渡將使用默認動畫。 - UIKit構造過渡語境。UIKit通過調用向動畫控制器詢問動畫的持續時間
transitionDuration(using:)
。UIKitanimateTransition(using:)
在動畫控制器上調用以執行過渡的動畫。 - 最後,動畫控制器調用
completeTransition(_:)
過渡上下文以指示動畫已完成。
dimiss過渡動畫的步驟幾乎相同。
在這種情況下,UIKit向fromVC
視圖控制器(正在關閉的視圖控制器)請求其過渡代理,要求提供動畫控制器animationController(forDismissed:)
。
非交互式過渡動畫需要提供的條件
- 設置過渡動畫代理。設置(目標視圖控制器)的
transitioningDelegate
屬性,即設置過渡動畫代理對象,該代理對象遵循UIViewControllerTransitioningDelegate
協議,實現forPresented
和forDismissed
兩個方法,分別提供present和dismiss的視圖控制器實例。 - 創建動畫控制器。創建present和dismiss的動畫控制器,實現動畫持續時間和構造動畫方法。
這裏我們完成一個簡單的從左到右的過渡動畫。
1. 構建present/dimiss場景
這裏不再講述構建過程,無論是stroyboard還是純代碼都是很好完成的。這裏爲了方便,使用storyboard完成。
左視圖控制器爲:ViewController
右視圖控制器爲:LeftViewController
2. 創建Animation Controller
Animation Controller是自定義過渡動畫的核心對象。
AnimationControlle繼承至NSObject
,遵循UIViewControllerAnimatedTransitioning
協議,實現兩個required方法。
class AnimationController: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
//要求提供動畫師對象的動畫持續時間屬性
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
//過渡動畫的實現效果,也是自定義過渡動畫的核心方法,需要構建動畫的實現。
}
}
這裏貼出Present狀態下的AnimationController的代碼,Dismiss狀態與其類似(動畫過程執行反過程)。
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:)
方法中設置動畫的持續時間。
在第二個transitionDuration(using:)
方法中構建動畫。
- 獲取過渡動畫所需的視圖控制器及snapshot。從過渡語境中
transitionContext.viewController
我們可以獲取源視圖控制器fromVC和目標視圖控制器toVC,過渡語境封裝了設計的視圖控制器的信息,極大地幫助我們處理視圖控制器的動畫轉化。還可以獲取fromVC和toVC的snapshot(屏幕快照),來構造更加複雜和優秀的動畫。 - 管理過渡語境的容器視圖 — containerView和視圖動畫的位置初始化。UIKit將整個過渡封裝在容器視圖中,以簡化視圖層次結構和動畫的管理,容器視圖負責管理
fromVC.view
和toVC.view
。由UIKit創建的容器視圖僅包含fromVC視圖。您必須添加任何其他將參與過渡的視圖。
addSubview(_:)將新視圖置於視圖層次結構中的所有其他視圖之前,因此添加子視圖的順序很重要。
- 設置動畫效果。動畫有兩種實現方法,一種是基礎動畫
animate
,另一種是關鍵幀動畫animateKeyframes
。這裏只是簡單地將fromVC.view
從屏幕的左邊界外移動到屏幕中。注意:在動畫完成後需要調用completeTransition(_:)
通知UIKit動畫已完成。這將確保最終狀態是一致的。
3.設置過渡動畫代理
設置(目標視圖控制器)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
協議,實現forPresented
和forDismissed
方法。
extension ViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
//目標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()
}
}
這樣就構建好了一個簡單、基礎的自定義轉場過渡動畫。
交互式過渡動畫
交互式動畫會使使用者的動畫體驗更自然舒適而不顯得尖銳,給控制動畫過程留下了餘地。iOS原生APP設置中便有這樣的交互式動畫的例子。
過渡動畫的進度跟隨手指的滑動來啓動/終止轉場動畫,這樣可以帶來良好的用戶交互體驗。
交互式控制器的工作方式
交互控制器通過加快,減慢甚至反轉過渡的過程來響應觸摸事件或編程輸入。爲了啓用交互式轉換,轉換代理必須提供一個交互控制器。您已經制作了過渡動畫。在過渡動畫的基礎上,Apple將交互式動畫封裝成交互控制器將響應手勢來管理此動畫,而不是讓其像視頻一樣播放。Apple提供了現成的UIPercentDrivenInteractiveTransition
類,通過繼承該類來創建自己的交互式控制器。
1. 創建交互式控制器
我們在之前過渡動畫的基礎上構建交互式過渡動畫,我們首先需要創建交互式控制器。這裏貼出顯示交互式控制器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屬性,表示交互式場景是否在發生。shouldCompleteTrantision
Bool屬性,表示是否應該終止過渡動畫。用於內部管理過渡。viewController
和toViewController
,獲取源視圖控制器和目標視圖控制器的引用,用於管理過渡某狀態present視圖控制器,達到交互式控制器與動畫控制器相聯繫的作用。prepareGestureRecognizer(in:)
爲源視圖添加屏幕手勢的方法,這裏的交互式動畫爲從左往右present出VC,所以爲源視圖添加屏幕相應在.left
的手勢。handleGsture(_:)
爲相應手勢變化從而改變過渡動畫狀態的方法。通過聲明局部變量以跟蹤滑動進度translation
,根據translation
在視圖中獲取並計算過渡進度progress
。手勢開始時,您將設置interactionInProgress
爲true
並觸發prsent
視圖控制器。手勢移動時,調用update(_:)
更新過渡進度。這是一種根據UIPercentDrivenInteractiveTransition
您傳入的百分比移動過渡的方法。如果取消手勢,則更新interactionInProgress
並取消過渡。一旦動作已經結束,您使用的過渡進度來決定cancel()
或finish()
。
2. 控制器聯繫
(源)視圖控制器與交互控制器相聯
在viewController
中添加以下屬性:
var presentInteractionController: PresentInteractionController?
並在viewDidLoad()
方法中初始化屬性:
presentInteractionController = PresentInteractionController(viewController: self, toViewController: leftViewController)
交互控制器與動畫控制器相聯
在PresentAnimationController
中添加以下屬性:
let interactionController: PresentInteractionController?
並添加init
方法:
init(interactionController: PresentInteractionController?) {
self.interactionController = interactionController
}
3. 實現過渡代理協議方法
動畫控制器forPresented
方法中修改PresentAnimationController
對象的創建。
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
//展現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
。如果是這樣,它將獲取關聯的交互控制器的引用,並驗證用戶交互正在進行中。如果不滿足這些條件中的任何一個,它將返回nil
以便轉換將繼續進行而不會發生交互。否則,它將交互控制器交還給UIKit,以便它可以管理過渡。
dismiss
狀態的交互式控制器的方法也是相近的,最終實現效果如下:
以上便是自定義過渡動畫的全部內容,讀者學習完後可以學一些進階動畫,參考一些優秀App的轉場動畫,進而創建自己過渡動畫,從而獲得良好的用戶體驗。