IOS實(shí)戰(zhàn)之自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)詳解
轉(zhuǎn)場(chǎng)動(dòng)畫(huà)這事,說(shuō)簡(jiǎn)單也簡(jiǎn)單,可以通過(guò)presentViewController:animated:completion:和dismissViewControllerAnimated:completion:這一組函數(shù)以模態(tài)視圖的方式展現(xiàn)、隱藏視圖。如果用到了navigationController,還可以調(diào)用pushViewController:animated:和popViewController這一組函數(shù)將新的視圖控制器壓棧、彈棧。
下圖中所有轉(zhuǎn)場(chǎng)動(dòng)畫(huà)都是自定義的動(dòng)畫(huà),這些效果如果不用自定義動(dòng)畫(huà)則很難甚至無(wú)法實(shí)現(xiàn):

由于錄屏的原因,有些效果無(wú)法完全展現(xiàn),比如它其實(shí)還支持橫屏。
自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的效果實(shí)現(xiàn)起來(lái)比較復(fù)雜,如果僅僅是拷貝一份能夠運(yùn)行的代碼卻不懂其中原理,就有可能帶來(lái)各種隱藏的bug。本文由淺入深介紹下面幾個(gè)知識(shí):
1、傳統(tǒng)的基于閉包的實(shí)現(xiàn)方式及其缺點(diǎn)
2、自定義present轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
3、交互式(Interactive)轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
4、轉(zhuǎn)場(chǎng)協(xié)調(diào)器與UIModalPresentationCustom
5、UINavigationController轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
在開(kāi)始正式的教程前,您首先需要下載demo,在代碼面前文字是蒼白的,demo中包含的注釋足以解釋本文所有的知識(shí)點(diǎn)。其次,您還得了解這幾個(gè)背景知識(shí)。
From和To
在代碼和文字中,經(jīng)常會(huì)出現(xiàn)fromView和toView。如果錯(cuò)誤的理解它們的含義會(huì)導(dǎo)致動(dòng)畫(huà)邏輯完全錯(cuò)誤。fromView表示當(dāng)前視圖,toView表示要跳轉(zhuǎn)到的視圖。如果是從A視圖控制器present到B,則A是from,B是to。從B視圖控制器dismiss到A時(shí),B變成了from,A是to。用一張圖表示:

Presented和Presengting
這也是一組相對(duì)的概念,它容易與fromView和toView混淆。簡(jiǎn)單來(lái)說(shuō),它不受present或dismiss的影響,如果是從A視圖控制器present到B,那么A總是presentedViewController,B總是presentingViewController。
modalPresentationStyle
這是一個(gè)枚舉類(lèi)型,表示present時(shí)動(dòng)畫(huà)的類(lèi)型。其中可以自定義動(dòng)畫(huà)效果的只有兩種:FullScreen和Custom,兩者的區(qū)別在于FullScreen會(huì)移除fromView,而Custom不會(huì)。比如文章開(kāi)頭的gif中,第三個(gè)動(dòng)畫(huà)效果就是Custom。
基于block的動(dòng)畫(huà)
最簡(jiǎn)單的轉(zhuǎn)場(chǎng)動(dòng)畫(huà)是使用transitionFromViewController方法:

這個(gè)方法雖然已經(jīng)過(guò)時(shí),但是對(duì)它的分析有助于后面知識(shí)的理解。它一共有6個(gè)參數(shù),前兩個(gè)表示從哪個(gè)VC開(kāi)始,跳轉(zhuǎn)到哪個(gè)VC,中間兩個(gè)參數(shù)表示動(dòng)畫(huà)的時(shí)間和選項(xiàng)。最后兩個(gè)參數(shù)表示動(dòng)畫(huà)的具體實(shí)現(xiàn)細(xì)節(jié)和回調(diào)閉包。
這六個(gè)參數(shù)其實(shí)就是一次轉(zhuǎn)場(chǎng)動(dòng)畫(huà)所必備的六個(gè)元素。它們可以分為兩組,前兩個(gè)參數(shù)為一組,表示頁(yè)面的跳轉(zhuǎn)關(guān)系,后面四個(gè)為一組,表示動(dòng)畫(huà)的執(zhí)行邏輯。
這個(gè)方法的缺點(diǎn)之一是可自定義程度不高(在后面您會(huì)發(fā)現(xiàn)能自定義的不僅僅是動(dòng)畫(huà)方式),另一個(gè)缺點(diǎn)則是重用性不好,也可以說(shuō)是耦合度比較大。
在最后兩個(gè)閉包參數(shù)中,可以預(yù)見(jiàn)的是fromViewController和toViewController參數(shù)都會(huì)被用到,而且他們是動(dòng)畫(huà)的關(guān)鍵。假設(shè)視圖控制器A可以跳轉(zhuǎn)到B、C、D、E、F,而且跳轉(zhuǎn)動(dòng)畫(huà)基本相似,您會(huì)發(fā)現(xiàn)transitionFromViewController方法要被復(fù)制多次,每次只會(huì)修改少量?jī)?nèi)容。
自定義present轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
出于解耦和提高可自定義程度的考慮,我們來(lái)學(xué)習(xí)轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的正確使用姿勢(shì)。
首先要了解一個(gè)關(guān)鍵概念:轉(zhuǎn)場(chǎng)動(dòng)畫(huà)代理,它是一個(gè)實(shí)現(xiàn)了UIViewControllerTransitioningDelegate協(xié)議的對(duì)象。我們需要自己實(shí)現(xiàn)這個(gè)對(duì)象,它的作用是為UIKit提供以下幾個(gè)對(duì)象中的一個(gè)或多個(gè):
1、Animator:
它是實(shí)現(xiàn)了UIViewControllerAnimatedTransitioning協(xié)議的對(duì)象,用于控制動(dòng)畫(huà)的持續(xù)時(shí)間和動(dòng)畫(huà)展示邏輯,代理可以為present和dismiss過(guò)程分別提供Animator,也可以提供同一個(gè)Animator。
交互式Animator:和Animator類(lèi)似,不過(guò)它是交互式的,后面會(huì)有詳細(xì)介紹
Presentation控制器:
它可以對(duì)present過(guò)程更加徹底的自定義,比如修改被展示視圖的大小,新增自定義視圖等,后面會(huì)有詳細(xì)介紹。

在這一小節(jié)中,我們首先介紹最簡(jiǎn)單的Animator?;仡櫼幌罗D(zhuǎn)場(chǎng)動(dòng)畫(huà)必備的6個(gè)元素,它們被分為兩組,彼此之間沒(méi)有關(guān)聯(lián)。Animator的作用等同于第二組的四個(gè)元素,也就是說(shuō)對(duì)于同一個(gè)Animator,可以適用于A跳轉(zhuǎn)B,也可以適用于A跳轉(zhuǎn)C。它表示一種通用的頁(yè)面跳轉(zhuǎn)時(shí)的動(dòng)畫(huà)邏輯,不受限于具體的視圖控制器。
如果您讀懂了這段話,整個(gè)自定義的轉(zhuǎn)場(chǎng)動(dòng)畫(huà)邏輯就很清楚了,以視圖控制器A跳轉(zhuǎn)到B為例:
- 創(chuàng)建動(dòng)畫(huà)代理,在事情比較簡(jiǎn)單時(shí),A自己就可以作為代理
- 設(shè)置B的transitioningDelegate為步驟1中創(chuàng)建的代理對(duì)象
- 調(diào)用presentViewController:animated:completion:并把參數(shù)animated設(shè)置為true
- 系統(tǒng)會(huì)找到代理中提供的Animator,由Animator負(fù)責(zé)動(dòng)畫(huà)邏輯
用具體的例子解釋就是:
// 這個(gè)類(lèi)相當(dāng)于A
class CrossDissolveFirstViewController: UIViewController, UIViewControllerTransitioningDelegate {
// 這個(gè)對(duì)象相當(dāng)于B
crossDissolveSecondViewController.transitioningDelegate = self
// 點(diǎn)擊按鈕觸發(fā)的函數(shù)
func animationButtonDidClicked() {
self.presentViewController(crossDissolveSecondViewController,
animated: true, completion: nil)
}
// 下面這兩個(gè)函數(shù)定義在UIViewControllerTransitioningDelegate協(xié)議中
// 用于為present和dismiss提供animator
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
// 也可以使用CrossDissolveAnimator,動(dòng)畫(huà)效果各有不同
// return CrossDissolveAnimator()
return HalfWaySpringAnimator()
}
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return CrossDissolveAnimator()
}
}
動(dòng)畫(huà)的關(guān)鍵在于animator如何實(shí)現(xiàn),它實(shí)現(xiàn)了UIViewControllerAnimatedTransitioning協(xié)議,至少需要實(shí)現(xiàn)兩個(gè)方法,我建議您仔細(xì)閱讀animateTransition方法中的注釋?zhuān)钦麄€(gè)動(dòng)畫(huà)邏輯的核心:
class HalfWaySpringAnimator: NSObject, UIViewControllerAnimatedTransitioning {
/// 設(shè)置動(dòng)畫(huà)的持續(xù)時(shí)間
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 2
}
/// 設(shè)置動(dòng)畫(huà)的進(jìn)行方式,附有詳細(xì)注釋?zhuān)琩emo中其他地方的這個(gè)方法不再解釋
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
let containerView = transitionContext.containerView()
// 需要關(guān)注一下from/to和presented/presenting的關(guān)系
// For a Presentation:
// fromView = The presenting view.
// toView = The presented view.
// For a Dismissal:
// fromView = The presented view.
// toView = The presenting view.
var fromView = fromViewController?.view
var toView = toViewController?.view
// iOS8引入了viewForKey方法,盡可能使用這個(gè)方法而不是直接訪問(wèn)controller的view屬性
// 比如在form sheet樣式中,我們?yōu)閜resentedViewController的view添加陰影或其他decoration,animator會(huì)對(duì)整個(gè)decoration view
// 添加動(dòng)畫(huà)效果,而此時(shí)presentedViewController的view只是decoration view的一個(gè)子視圖
if transitionContext.respondsToSelector(Selector("viewForKey:")) {
fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)
toView = transitionContext.viewForKey(UITransitionContextToViewKey)
}
// 我們讓toview的origin.y在屏幕的一半處,這樣它從屏幕的中間位置彈起而不是從屏幕底部彈起,彈起過(guò)程中逐漸變?yōu)椴煌该?
toView?.frame = CGRectMake(fromView!.frame.origin.x, fromView!.frame.maxY / 2, fromView!.frame.width, fromView!.frame.height)
toView?.alpha = 0.0
// 在present和,dismiss時(shí),必須將toview添加到視圖層次中
containerView?.addSubview(toView!)
let transitionDuration = self.transitionDuration(transitionContext)
// 使用spring動(dòng)畫(huà),有彈簧效果,動(dòng)畫(huà)結(jié)束后一定要調(diào)用completeTransition方法
UIView.animateWithDuration(transitionDuration, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0, options: .CurveLinear, animations: { () -> Void in
toView!.alpha = 1.0 // 逐漸變?yōu)椴煌该?
toView?.frame = transitionContext.finalFrameForViewController(toViewController!) // 移動(dòng)到指定位置
}) { (finished: Bool) -> Void in
let wasCancelled = transitionContext.transitionWasCancelled()
transitionContext.completeTransition(!wasCancelled)
}
}
}
animateTransition方法的核心則是從轉(zhuǎn)場(chǎng)動(dòng)畫(huà)上下文獲取必要的信息以完成動(dòng)畫(huà)。上下文是一個(gè)實(shí)現(xiàn)了UIViewControllerContextTransitioning的對(duì)象,它的作用在于為animateTransition方法提供必備的信息。您不應(yīng)該緩存任何關(guān)于動(dòng)畫(huà)的信息,而是應(yīng)該總是從轉(zhuǎn)場(chǎng)動(dòng)畫(huà)上下文中獲取(比如fromView和toView),這樣可以保證總是獲取到最新的、正確的信息。

獲取到足夠信息后,我們調(diào)用UIView.animateWithDuration方法把動(dòng)畫(huà)交給Core Animation處理。千萬(wàn)不要忘記在動(dòng)畫(huà)調(diào)用結(jié)束后,執(zhí)行completeTransition方法。
本節(jié)的知識(shí)在Demo的Cross Dissolve文件夾中有詳細(xì)的代碼。其中有兩個(gè)animator文件,這說(shuō)明我們可以為present和dismiss提供同一個(gè)animator,或者分別提供各自對(duì)應(yīng)的animator。如果兩者動(dòng)畫(huà)效果類(lèi)似,您可以共用同一個(gè)animator,惟一的區(qū)別在于:
- present時(shí),要把toView加入到container的視圖層級(jí)。
- dismiss時(shí),要把fromView從container的視圖層級(jí)中移除。
如果您被前面這一大段代碼和知識(shí)弄暈了,或者暫時(shí)用不到這些具體的知識(shí),您至少需要記住自定義動(dòng)畫(huà)的基本原理和流程:
- 設(shè)置將要跳轉(zhuǎn)到的視圖控制器(presentedViewController)的transitioningDelegate
- 充當(dāng)代理的對(duì)象可以是源視圖控制器(presentingViewController),也可以是自己創(chuàng)建的對(duì)象,它需要為轉(zhuǎn)場(chǎng)動(dòng)畫(huà)提供一個(gè)animator對(duì)象。
- animator對(duì)象的animateTransition是整個(gè)動(dòng)畫(huà)的核心邏輯。
交互式(Interactive)轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
剛剛我們說(shuō)到,設(shè)置了toViewController的transitioningDelegate屬性并且present時(shí),UIKit會(huì)從代理處獲取animator,其實(shí)這里還有一個(gè)細(xì)節(jié):UIKit還會(huì)調(diào)用代理的interactionControllerForPresentation:方法來(lái)獲取交互式控制器,如果得到了nil則執(zhí)行非交互式動(dòng)畫(huà),這就回到了上一節(jié)的內(nèi)容。
如果獲取到了不是nil的對(duì)象,那么UIKit不會(huì)調(diào)用animator的animateTransition方法,而是調(diào)用交互式控制器(還記得前面介紹動(dòng)畫(huà)代理的示意圖么,交互式動(dòng)畫(huà)控制器和animator是平級(jí)關(guān)系)的startInteractiveTransition:方法。
所謂的交互式動(dòng)畫(huà),通常是基于手勢(shì)驅(qū)動(dòng),產(chǎn)生一個(gè)動(dòng)畫(huà)完成的百分比來(lái)控制動(dòng)畫(huà)效果(文章開(kāi)頭的gif中第二個(gè)動(dòng)畫(huà)效果)。整個(gè)動(dòng)畫(huà)不再是一次性、連貫的完成,而是在任何時(shí)候都可以改變百分比甚至取消。這需要一個(gè)實(shí)現(xiàn)了UIPercentDrivenInteractiveTransition協(xié)議的交互式動(dòng)畫(huà)控制器和animator協(xié)同工作。這看上去是一個(gè)非常復(fù)雜的任務(wù),但UIKit已經(jīng)封裝了足夠多細(xì)節(jié),我們只需要在交互式動(dòng)畫(huà)控制器和中定義一個(gè)時(shí)間處理函數(shù)(比如處理滑動(dòng)手勢(shì)),然后在接收到新的事件時(shí),計(jì)算動(dòng)畫(huà)完成的百分比并且調(diào)用updateInteractiveTransition來(lái)更新動(dòng)畫(huà)進(jìn)度即可。
用下面這段代碼簡(jiǎn)單表示一下整個(gè)流程(刪除了部分細(xì)節(jié)和注釋?zhuān)?qǐng)不要以此為正確參考),完整的代碼請(qǐng)參考demo中的Interactivity文件夾:
// 這個(gè)相當(dāng)于fromViewController
class InteractivityFirstViewController: UIViewController {
// 這個(gè)相當(dāng)于toViewController
lazy var interactivitySecondViewController: InteractivitySecondViewController = InteractivitySecondViewController()
// 定義了一個(gè)InteractivityTransitionDelegate類(lèi)作為代理
lazy var customTransitionDelegate: InteractivityTransitionDelegate = InteractivityTransitionDelegate()
override func viewDidLoad() {
super.viewDidLoad()
setupView() // 主要是一些UI控件的布局,可以無(wú)視其實(shí)現(xiàn)細(xì)節(jié)
/// 設(shè)置動(dòng)畫(huà)代理,這個(gè)代理比較復(fù)雜,所以我們新建了一個(gè)代理對(duì)象而不是讓self作為代理
interactivitySecondViewController.transitioningDelegate = customTransitionDelegate
}
// 觸發(fā)手勢(shì)時(shí),也會(huì)調(diào)用animationButtonDidClicked方法
func interactiveTransitionRecognizerAction(sender: UIScreenEdgePanGestureRecognizer) {
if sender.state == .Began {
self.animationButtonDidClicked(sender)
}
}
func animationButtonDidClicked(sender: AnyObject) {
self.presentViewController(interactivitySecondViewController, animated: true, completion: nil)
}
}
非交互式的動(dòng)畫(huà)代理只需要為present和dismiss提供animator即可,但是在交互式的動(dòng)畫(huà)代理中,還需要為present和dismiss提供交互式動(dòng)畫(huà)控制器:
class InteractivityTransitionDelegate: NSObject, UIViewControllerTransitioningDelegate {
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return InteractivityTransitionAnimator(targetEdge: targetEdge)
}
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return InteractivityTransitionAnimator(targetEdge: targetEdge)
}
/// 前兩個(gè)函數(shù)和淡入淡出demo中的實(shí)現(xiàn)一致
/// 后兩個(gè)函數(shù)用于實(shí)現(xiàn)交互式動(dòng)畫(huà)
func interactionControllerForPresentation(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return TransitionInteractionController(gestureRecognizer: gestureRecognizer, edgeForDragging: targetEdge)
}
func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return TransitionInteractionController(gestureRecognizer: gestureRecognizer, edgeForDragging: targetEdge)
}
}
animator中的代碼略去,它和非交互式動(dòng)畫(huà)中的animator類(lèi)似。因?yàn)榻换ナ降膭?dòng)畫(huà)只是一種錦上添花,它必須支持非交互式的動(dòng)畫(huà),比如這個(gè)例子中,點(diǎn)擊按鈕依然出發(fā)的是非交互式的動(dòng)畫(huà),只是手勢(shì)滑動(dòng)才會(huì)觸發(fā)交互式動(dòng)畫(huà)。
class TransitionInteractionController: UIPercentDrivenInteractiveTransition {
/// 當(dāng)手勢(shì)有滑動(dòng)時(shí)觸發(fā)這個(gè)函數(shù)
func gestureRecognizeDidUpdate(gestureRecognizer: UIScreenEdgePanGestureRecognizer) {
switch gestureRecognizer.state {
case .Began: break
case .Changed: self.updateInteractiveTransition(self.percentForGesture(gestureRecognizer)) //手勢(shì)滑動(dòng),更新百分比
case .Ended: // 滑動(dòng)結(jié)束,判斷是否超過(guò)一半,如果是則完成剩下的動(dòng)畫(huà),否則取消動(dòng)畫(huà)
if self.percentForGesture(gestureRecognizer) >= 0.5 {
self.finishInteractiveTransition()
}
else {
self.cancelInteractiveTransition()
}
default: self.cancelInteractiveTransition()
}
}
private func percentForGesture(gesture: UIScreenEdgePanGestureRecognizer) -> CGFloat {
let percent = 根據(jù)gesture計(jì)算得出
return percent
}
}
交互式動(dòng)畫(huà)是在非交互式動(dòng)畫(huà)的基礎(chǔ)上實(shí)現(xiàn)的,我們需要?jiǎng)?chuàng)建一個(gè)繼承自UIPercentDrivenInteractiveTransition類(lèi)型的子類(lèi),并且在動(dòng)畫(huà)代理中返回這個(gè)類(lèi)型的實(shí)例對(duì)象。
在這個(gè)類(lèi)型中,監(jiān)聽(tīng)手勢(shì)(或者下載進(jìn)度等等)的時(shí)間變化,然后調(diào)用percentForGesture方法更新動(dòng)畫(huà)進(jìn)度即可。
轉(zhuǎn)場(chǎng)協(xié)調(diào)器與UIModalPresentationCustom
在進(jìn)行轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的同時(shí),您還可以進(jìn)行一些同步的,額外的動(dòng)畫(huà),比如文章開(kāi)頭gif中的第三個(gè)例子。presentedView和presentingView可以更改自身的視圖層級(jí),添加額外的效果(陰影,圓角)。UIKit使用轉(zhuǎn)成協(xié)調(diào)器來(lái)管理這些額外的動(dòng)畫(huà)。您可以通過(guò)需要產(chǎn)生動(dòng)畫(huà)效果的視圖控制器的transitionCoordinator屬性來(lái)獲取轉(zhuǎn)場(chǎng)協(xié)調(diào)器,轉(zhuǎn)場(chǎng)協(xié)調(diào)器只在轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的執(zhí)行過(guò)程中存在。

想要完成gif中第三個(gè)例子的效果,我們還需要使用UIModalPresentationStyle.Custom來(lái)代替.FullScreen。因?yàn)楹笳邥?huì)移除fromViewController,這顯然不符合需求。
當(dāng)present的方式為.Custom時(shí),我們還可以使用UIPresentationController更加徹底的控制轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的效果。一個(gè) presentation controller具備以下幾個(gè)功能:
- 設(shè)置presentedViewController的視圖大小
- 添加自定義視圖來(lái)改變presentedView的外觀
- 為任何自定義的視圖提供轉(zhuǎn)場(chǎng)動(dòng)畫(huà)效果
- 根據(jù)size class進(jìn)行響應(yīng)式布局
您可以認(rèn)為,. FullScreen以及其他present風(fēng)格都是swift為我們實(shí)現(xiàn)提供好的,它們是.Custom的特例。而.Custom允許我們更加自由的定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)效果。
UIPresentationController提供了四個(gè)函數(shù)來(lái)定義present和dismiss動(dòng)畫(huà)開(kāi)始前后的操作:
- presentationTransitionWillBegin: present將要執(zhí)行時(shí)
- presentationTransitionDidEnd:present執(zhí)行結(jié)束后
- dismissalTransitionWillBegin:dismiss將要執(zhí)行時(shí)
- dismissalTransitionDidEnd:dismiss執(zhí)行結(jié)束后
下面的代碼簡(jiǎn)要描述了gif中第三個(gè)動(dòng)畫(huà)效果的實(shí)現(xiàn)原理,您可以在demo的Custom Presentation文件夾下查看完成代碼:
// 這個(gè)相當(dāng)于fromViewController
class CustomPresentationFirstViewController: UIViewController {
// 這個(gè)相當(dāng)于toViewController
lazy var customPresentationSecondViewController: CustomPresentationSecondViewController = CustomPresentationSecondViewController()
// 創(chuàng)建PresentationController
lazy var customPresentationController: CustomPresentationController = CustomPresentationController(presentedViewController: self.customPresentationSecondViewController, presentingViewController: self)
override func viewDidLoad() {
super.viewDidLoad()
setupView() // 主要是一些UI控件的布局,可以無(wú)視其實(shí)現(xiàn)細(xì)節(jié)
// 設(shè)置轉(zhuǎn)場(chǎng)動(dòng)畫(huà)代理
customPresentationSecondViewController.transitioningDelegate = customPresentationController
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func animationButtonDidClicked() {
self.presentViewController(customPresentationSecondViewController, animated: true, completion: nil)
}
}
重點(diǎn)在于如何實(shí)現(xiàn)CustomPresentationController這個(gè)類(lèi):
class CustomPresentationController: UIPresentationController, UIViewControllerTransitioningDelegate {
var presentationWrappingView: UIView? // 這個(gè)視圖封裝了原視圖,添加了陰影和圓角效果
var dimmingView: UIView? = nil // alpha為0.5的黑色蒙版
// 告訴UIKit為哪個(gè)視圖添加動(dòng)畫(huà)效果
override func presentedView() -> UIView? {
return self.presentationWrappingView
}
}
// 四個(gè)方法自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)發(fā)生前后的操作
extension CustomPresentationController {
override func presentationTransitionWillBegin() {
// 設(shè)置presentationWrappingView和dimmingView的UI效果
let transitionCoordinator = self.presentingViewController.transitionCoordinator()
self.dimmingView?.alpha = 0
// 通過(guò)轉(zhuǎn)場(chǎng)協(xié)調(diào)器執(zhí)行同步的動(dòng)畫(huà)效果
transitionCoordinator?.animateAlongsideTransition({ (context: UIViewControllerTransitionCoordinatorContext) -> Void in
self.dimmingView?.alpha = 0.5
}, completion: nil)
}
/// present結(jié)束時(shí),把dimmingView和wrappingView都清空,這些臨時(shí)視圖用不到了
override func presentationTransitionDidEnd(completed: Bool) {
if !completed {
self.presentationWrappingView = nil
self.dimmingView = nil
}
}
/// dismiss開(kāi)始時(shí),讓dimmingView完全透明,這個(gè)動(dòng)畫(huà)和animator中的動(dòng)畫(huà)同時(shí)發(fā)生
override func dismissalTransitionWillBegin() {
let transitionCoordinator = self.presentingViewController.transitionCoordinator()
transitionCoordinator?.animateAlongsideTransition({ (context: UIViewControllerTransitionCoordinatorContext) -> Void in
self.dimmingView?.alpha = 0
}, completion: nil)
}
/// dismiss結(jié)束時(shí),把dimmingView和wrappingView都清空,這些臨時(shí)視圖用不到了
override func dismissalTransitionDidEnd(completed: Bool) {
if completed {
self.presentationWrappingView = nil
self.dimmingView = nil
}
}
}
extension CustomPresentationController {
}
除此以外,這個(gè)類(lèi)還要處理子視圖布局相關(guān)的邏輯。它作為動(dòng)畫(huà)代理,還需要為動(dòng)畫(huà)提供animator對(duì)象,詳細(xì)代碼請(qǐng)?jiān)赿emo的Custom Presentation文件夾下閱讀。
UINavigationController轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
到目前為止,所有轉(zhuǎn)場(chǎng)動(dòng)畫(huà)都是適用于present和dismiss的,其實(shí)UINavigationController也可以自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)。兩者是平行關(guān)系,很多都可以類(lèi)比過(guò)來(lái):
class FromViewController: UIViewController, UINavigationControllerDelegate {
let toViewController: ToViewController = ToViewController()
override func viewDidLoad() {
super.viewDidLoad()
setupView() // 主要是一些UI控件的布局,可以無(wú)視其實(shí)現(xiàn)細(xì)節(jié)
self.navigationController.delegate = self
}
}
與present/dismiss不同的時(shí),現(xiàn)在視圖控制器實(shí)現(xiàn)的是UINavigationControllerDelegate協(xié)議,讓自己成為navigationController的代理。這個(gè)協(xié)議類(lèi)似于此前的UIViewControllerTransitioningDelegate協(xié)議。
FromViewController實(shí)現(xiàn)UINavigationControllerDelegate協(xié)議的具體操作如下:
func navigationController(navigationController: UINavigationController,
animationControllerForOperation operation: UINavigationControllerOperation,
fromViewController fromVC: UIViewController,
toViewController toVC: UIViewController)
-> UIViewControllerAnimatedTransitioning? {
if operation == .Push {
return PushAnimator()
}
if operation == .Pop {
return PopAnimator()
}
return nil;
}
至于animator,就和此前沒(méi)有任何區(qū)別了??梢?jiàn),一個(gè)封裝得很好的animator,不僅能在present/dismiss時(shí)使用,甚至還可以在push/pop時(shí)使用。
UINavigationController也可以添加交互式轉(zhuǎn)場(chǎng)動(dòng)畫(huà),原理也和此前類(lèi)似。
總結(jié)
對(duì)于非交互式動(dòng)畫(huà),需要設(shè)置presentedViewController的transitioningDelegate屬性,這個(gè)代理需要為present和dismiss提供animator。在animator中規(guī)定了動(dòng)畫(huà)的持續(xù)時(shí)間和表現(xiàn)邏輯。
對(duì)于交互式動(dòng)畫(huà),需要在此前的基礎(chǔ)上,由transitioningDelegate屬性提供交互式動(dòng)畫(huà)控制器。在控制器中進(jìn)行事件處理,然后更新動(dòng)畫(huà)完成進(jìn)度。
對(duì)于自定義動(dòng)畫(huà),可以通過(guò)UIPresentationController中的四個(gè)函數(shù)自定義動(dòng)畫(huà)執(zhí)行前后的效果,可以修改presentedViewController的大小、外觀并同步執(zhí)行其他的動(dòng)畫(huà)。
自定義動(dòng)畫(huà)的水還是比較深,本文僅適合做入門(mén)學(xué)習(xí)用,歡迎互相交流。
相關(guān)文章
解析iOS開(kāi)發(fā)中的FirstResponder第一響應(yīng)對(duì)象
這篇文章主要介紹了解析iOS開(kāi)發(fā)中的FirstResponder第一響應(yīng)對(duì)象,包括View的FirstResponder的釋放問(wèn)題,需要的朋友可以參考下2015-10-10
iOS 自定義返回按鈕保留系統(tǒng)滑動(dòng)返回功能
這篇文章主要介紹了iOS 自定義返回按鈕,保留系統(tǒng)滑動(dòng)返回功能,實(shí)現(xiàn)方法非常簡(jiǎn)單,具有參考借鑒價(jià)值,需要的朋友參考下吧2017-01-01
iOS應(yīng)用開(kāi)發(fā)中StoryBoard搭建UI界面的基本使用講解
這篇文章主要介紹了iOS應(yīng)用開(kāi)發(fā)中StoryBoard搭建UI界面的基本使用,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2016-02-02
iOS代碼瘦身實(shí)踐之如何刪除無(wú)用的類(lèi)
這篇文章主要給大家介紹了關(guān)于iOS代碼瘦身實(shí)踐之如何刪除無(wú)用的類(lèi),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家各位iOS開(kāi)發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
使用UItableview在iOS應(yīng)用開(kāi)發(fā)中實(shí)現(xiàn)好友列表功能
這篇文章主要介紹了使用UItableview在iOS應(yīng)用開(kāi)發(fā)中實(shí)現(xiàn)一個(gè)好友列表功能的方法,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2015-12-12
IOS開(kāi)發(fā)中鍵盤(pán)輸入屏幕上移的解決方法
在IOS開(kāi)法中經(jīng)常會(huì)遇到鍵盤(pán)遮擋屏幕的事情,經(jīng)常檔住下面的按鈕,下面小編給大家分享IOS開(kāi)發(fā)中鍵盤(pán)輸入屏幕上移的解決方法,感興趣的朋友一起看看吧2016-10-10
iOS中Xcode 8 日志輸出亂碼問(wèn)題的解決方法
這篇文章主要介紹了iOS中Xcode 8日志輸出亂碼問(wèn)題及解決方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09
iOS實(shí)現(xiàn)多個(gè)垂直滑動(dòng)條并列視圖
這篇文章主要為大家詳細(xì)介紹了iOS實(shí)現(xiàn)多個(gè)垂直滑動(dòng)條并列視圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03

