轉場動畫這事,說簡略也簡略,可以經由過程presentViewController:animated:completion:和dismissViewControllerAnimated:completion:這一組函數以模態視圖的方法展示、隱蔽視圖。假如用到了navigationController,還可以挪用pushViewController:animated:和popViewController這一組函數將新的視圖掌握器壓棧、彈棧。
下圖中一切轉場動畫都是自界說的動畫,這些後果假如不消自界說動畫則很難乃至沒法完成:
因為錄屏的緣由,有些後果沒法完整展示,好比它其實還支撐橫屏。
自界說轉場動畫的後果完成起來比擬龐雜,假如僅僅是拷貝一份可以或許運轉的代碼卻不懂個中道理,就有能夠帶來各類隱蔽的bug。本文由淺入深引見上面幾個常識:
1、傳統的基於閉包的完成方法及其缺陷
2、自界說present轉場動畫
3、交互式(Interactive)轉場動畫
4、轉場調和器與UIModalPresentationCustom
5、UINavigationController轉場動畫
在開端正式的教程前,您起首須要下載demo,在代碼眼前文字是慘白的,demo中包括的正文足以說明本文一切的常識點。其次,您還得懂得這幾個配景常識。
From和To
在代碼和文字中,常常會湧現fromView和toView。假如毛病的懂得它們的寄義會招致動畫邏輯完整毛病。fromView表現以後視圖,toView表現要跳轉到的視圖。假如是從A視圖掌握器present到B,則A是from,B是to。從B視圖掌握器dismiss到A時,B釀成了from,A是to。用一張圖表現:
Presented和Presengting
這也是一組絕對的概念,它輕易與fromView和toView混雜。簡略來講,它不受present或dismiss的影響,假如是從A視圖掌握器present到B,那末A老是presentedViewController,B老是presentingViewController。
modalPresentationStyle
這是一個列舉類型,表現present時動畫的類型。個中可以自界說動畫後果的只要兩種:FullScreen和Custom,二者的差別在於FullScreen會移除fromView,而Custom不會。好比文章開首的gif中,第三個動畫後果就是Custom。
基於block的動畫
最簡略的轉場動畫是應用transitionFromViewController辦法:
這個辦法固然曾經過時,然則對它的剖析有助於前面常識的懂得。它一共有6個參數,前兩個表現從哪一個VC開端,跳轉到哪一個VC,中央兩個參數表現動畫的時光和選項。最初兩個參數表現動畫的詳細完成細節和回調閉包。
這六個參數其實就是一次轉場動畫所必備的六個元素。它們可以分為兩組,前兩個參數為一組,表現頁面的跳轉關系,前面四個為一組,表現動畫的履行邏輯。
這個辦法的缺陷之一是可自界說水平不高(在前面您會發明能自界說的不只僅是動畫方法),另外一個缺陷則是重用性欠好,也能夠說是耦合度比擬年夜。
在最初兩個閉包參數中,可以預感的是fromViewController和toViewController參數都邑被用到,並且他們是動畫的症結。假定視圖掌握器A可以跳轉到B、C、D、E、F,並且跳遷移轉變畫根本類似,您會發明transitionFromViewController辦法要被復制屢次,每次只會修正大批內容。
自界說present轉場動畫
出於解耦和進步可自界說水平的斟酌,我們來進修轉場動畫的准確應用姿態。
起首要懂得一個症結概念:轉場動畫署理,它是一個完成了UIViewControllerTransitioningDelegate協定的對象。我們須要本身完成這個對象,它的感化是為UIKit供給以下幾個對象中的一個或多個:
1、Animator:
它是完成了UIViewControllerAnimatedTransitioning協定的對象,用於掌握動畫的連續時光和動畫展現邏輯,署理可認為present和dismiss進程分離供給Animator,也能夠供給統一個Animator。
交互式Animator:和Animator相似,不外它是交互式的,前面會有具體引見
Presentation掌握器:
它可以對present進程加倍完全的自界說,好比修正被展現視圖的年夜小,新增自界說視圖等,前面會有具體引見。
在這一末節中,我們起首引見最簡略的Animator。回想一下轉場動畫必備的6個元素,它們被分為兩組,彼此之間沒有聯系關系。Animator的感化同等於第二組的四個元素,也就是說關於統一個Animator,可以實用於A跳轉B,也能夠實用於A跳轉C。它表現一種通用的頁面跳轉時的動畫邏輯,不受限於詳細的視圖掌握器。
假如您讀懂了這段話,全部自界說的轉場動畫邏輯就很清晰了,以視圖掌握器A跳轉到B為例:
器具體的例子說明就是:
// 這個類相當於A class CrossDissolveFirstViewController: UIViewController, UIViewControllerTransitioningDelegate { // 這個對象相當於B crossDissolveSecondViewController.transitioningDelegate = self // 點擊按鈕觸發的函數 func animationButtonDidClicked() { self.presentViewController(crossDissolveSecondViewController, animated: true, completion: nil) } // 上面這兩個函數界說在UIViewControllerTransitioningDelegate協定中 // 用於為present和dismiss供給animator func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { // 也能夠應用CrossDissolveAnimator,動畫後果各有分歧 // return CrossDissolveAnimator() return HalfWaySpringAnimator() } func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return CrossDissolveAnimator() } }
動畫的症結在於animator若何完成,它完成了UIViewControllerAnimatedTransitioning協定,至多須要完成兩個辦法,我建議您細心浏覽animateTransition辦法中的正文,它是全部動畫邏輯的焦點:
class HalfWaySpringAnimator: NSObject, UIViewControllerAnimatedTransitioning { /// 設置動畫的連續時光 func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 2 } /// 設置動畫的停止方法,附有具體正文,demo中其他處所的這個辦法不再說明 func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) let containerView = transitionContext.containerView() // 須要存眷一下from/to和presented/presenting的關系 // 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辦法,盡量應用這個辦法而不是直接拜訪controller的view屬性 // 好比在form sheet款式中,我們為presentedViewController的view添加暗影或其他decoration,animator會對全部decoration view // 添加動畫後果,而此時presentedViewController的view只是decoration view的一個子視圖 if transitionContext.respondsToSelector(Selector("viewForKey:")) { fromView = transitionContext.viewForKey(UITransitionContextFromViewKey) toView = transitionContext.viewForKey(UITransitionContextToViewKey) } // 我們讓toview的origin.y在屏幕的一半處,如許它從屏幕的中央地位彈起而不是從屏幕底部彈起,彈起進程中逐步變成不通明 toView?.frame = CGRectMake(fromView!.frame.origin.x, fromView!.frame.maxY / 2, fromView!.frame.width, fromView!.frame.height) toView?.alpha = 0.0 // 在present和,dismiss時,必需將toview添加到視圖條理中 containerView?.addSubview(toView!) let transitionDuration = self.transitionDuration(transitionContext) // 應用spring動畫,有彈簧後果,動畫停止後必定要挪用completeTransition辦法 UIView.animateWithDuration(transitionDuration, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0, options: .CurveLinear, animations: { () -> Void in toView!.alpha = 1.0 // 逐步變成不通明 toView?.frame = transitionContext.finalFrameForViewController(toViewController!) // 挪動到指定地位 }) { (finished: Bool) -> Void in let wasCancelled = transitionContext.transitionWasCancelled() transitionContext.completeTransition(!wasCancelled) } } }
animateTransition辦法的焦點則是從轉場動畫高低文獲得需要的信息以完成動畫。高低文是一個完成了UIViewControllerContextTransitioning的對象,它的感化在於為animateTransition辦法供給必備的信息。您不該該緩存任何干於動畫的信息,而是應當老是從轉場動畫高低文中獲得(好比fromView和toView),如許可以包管老是獲得到最新的、准確的信息。
獲得到足夠信息後,我們挪用UIView.animateWithDuration辦法把動畫交給Core Animation處置。萬萬不要忘卻在動畫挪用停止後,履行completeTransition辦法。
本節的常識在Demo的Cross Dissolve文件夾中有具體的代碼。個中有兩個animator文件,這解釋我們可認為present和dismiss供給統一個animator,或許分離供給各自對應的animator。假如二者動畫後果相似,您可以共用統一個animator,唯一的差別在於:
假如您被後面這一年夜段代碼和常識弄暈了,或許臨時用不到這些詳細的常識,您至多須要記住自界說動畫的根本道理和流程:
交互式(Interactive)轉場動畫
方才我們說到,設置了toViewController的transitioningDelegate屬性而且present時,UIKit會從署理處獲得animator,其實這裡還有一個細節:UIKit還會挪用署理的interactionControllerForPresentation:辦法來獲得交互式掌握器,假如獲得了nil則履行非交互式動畫,這就回到了上一節的內容。
假如獲得到了不是nil的對象,那末UIKit不會挪用animator的animateTransition辦法,而是挪用交互式掌握器(還記得後面引見動畫署理的表示圖麼,交互式動畫掌握器和animator是平級關系)的startInteractiveTransition:辦法。
所謂的交互式動畫,平日是基於手勢驅動,發生一個動畫完成的百分比來掌握動畫後果(文章開首的gif中第二個動畫後果)。全部動畫不再是一次性、連接的完成,而是在任什麼時候候都可以轉變百分比乃至撤消。這須要一個完成了UIPercentDrivenInteractiveTransition協定的交互式動畫掌握器和animator協同任務。這看上去是一個異常龐雜的義務,但UIKit曾經封裝了足夠多細節,我們只須要在交互式動畫掌握器和中界說一個時光處置函數(好比處置滑著手勢),然後在吸收到新的事宜時,盤算動畫完成的百分比而且挪用updateInteractiveTransition來更新動畫進度便可。
用上面這段代碼簡略表現一下全部流程(刪除部門細節和正文,請不要以此為准確參考),完全的代碼請參考demo中的Interactivity文件夾:
// 這個相當於fromViewController class InteractivityFirstViewController: UIViewController { // 這個相當於toViewController lazy var interactivitySecondViewController: InteractivitySecondViewController = InteractivitySecondViewController() // 界說了一個InteractivityTransitionDelegate類作為署理 lazy var customTransitionDelegate: InteractivityTransitionDelegate = InteractivityTransitionDelegate() override func viewDidLoad() { super.viewDidLoad() setupView() // 重要是一些UI控件的結構,可以疏忽其完成細節 /// 設置動畫署理,這個署理比擬龐雜,所以我們新建了一個署理對象而不是讓self作為署理 interactivitySecondViewController.transitioningDelegate = customTransitionDelegate } // 觸發手勢時,也會挪用animationButtonDidClicked辦法 func interactiveTransitionRecognizerAction(sender: UIScreenEdgePanGestureRecognizer) { if sender.state == .Began { self.animationButtonDidClicked(sender) } } func animationButtonDidClicked(sender: AnyObject) { self.presentViewController(interactivitySecondViewController, animated: true, completion: nil) } }
非交互式的動畫署理只須要為present和dismiss供給animator便可,然則在交互式的動畫署理中,還須要為present和dismiss供給交互式動畫掌握器:
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) } /// 前兩個函數和淡入淡出demo中的完成分歧 /// 後兩個函數用於完成交互式動畫 func interactionControllerForPresentation(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return TransitionInteractionController(gestureRecognizer: gestureRecognizer, edgeForDragging: targetEdge) } func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return TransitionInteractionController(gestureRecognizer: gestureRecognizer, edgeForDragging: targetEdge) } }
animator中的代碼略去,它和非交互式動畫中的animator相似。由於交互式的動畫只是一種如虎添翼,它必需支撐非交互式的動畫,好比這個例子中,點擊按鈕仍然動身的長短交互式的動畫,只是手勢滑動才會觸發交互式動畫。
class TransitionInteractionController: UIPercentDrivenInteractiveTransition { /// 當手勢有滑動時觸發這個函數 func gestureRecognizeDidUpdate(gestureRecognizer: UIScreenEdgePanGestureRecognizer) { switch gestureRecognizer.state { case .Began: break case .Changed: self.updateInteractiveTransition(self.percentForGesture(gestureRecognizer)) //手勢滑動,更新百分比 case .Ended: // 滑動停止,斷定能否跨越一半,假如是則完成剩下的動畫,不然撤消動畫 if self.percentForGesture(gestureRecognizer) >= 0.5 { self.finishInteractiveTransition() } else { self.cancelInteractiveTransition() } default: self.cancelInteractiveTransition() } } private func percentForGesture(gesture: UIScreenEdgePanGestureRecognizer) -> CGFloat { let percent = 依據gesture盤算得出 return percent } }
交互式動畫是在非交互式動畫的基本上完成的,我們須要創立一個繼續自UIPercentDrivenInteractiveTransition類型的子類,而且在動畫署理中前往這個類型的實例對象。
在這個類型中,監聽手勢(或許下載進度等等)的時光變更,然後挪用percentForGesture辦法更新動畫進度便可。
轉場調和器與UIModalPresentationCustom
在停止轉場動畫的同時,您還可以停止一些同步的,額定的動畫,好比文章開首gif中的第三個例子。presentedView和presentingView可以更改本身的視圖層級,添加額定的後果(暗影,圓角)。UIKit應用轉成調和器來治理這些額定的動畫。您可以經由過程須要發生動畫後果的視圖掌握器的transitionCoordinator屬性來獲得轉場調和器,轉場調和器只在轉場動畫的履行進程中存在。
想要完成gif中第三個例子的後果,我們還須要應用UIModalPresentationStyle.Custom來取代.FullScreen。由於後者會移除fromViewController,這明顯不相符需求。
當present的方法為.Custom時,我們還可使用UIPresentationController加倍完全的掌握轉場動畫的後果。一個 presentation controller具有以下幾個功效:
您可以以為,. FullScreen和其他present作風都是swift為我們完成供給好的,它們是.Custom的特例。而.Custom許可我們加倍自在的界說轉場動畫後果。
UIPresentationController供給了四個函數來界說present和dismiss動畫開端前後的操作:
上面的代碼扼要描寫了gif中第三個動畫後果的完成道理,您可以在demo的Custom Presentation文件夾下檢查完成代碼:
// 這個相當於fromViewController class CustomPresentationFirstViewController: UIViewController { // 這個相當於toViewController lazy var customPresentationSecondViewController: CustomPresentationSecondViewController = CustomPresentationSecondViewController() // 創立PresentationController lazy var customPresentationController: CustomPresentationController = CustomPresentationController(presentedViewController: self.customPresentationSecondViewController, presentingViewController: self) override func viewDidLoad() { super.viewDidLoad() setupView() // 重要是一些UI控件的結構,可以疏忽其完成細節 // 設置轉場動畫署理 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) } }
重點在於若何完成CustomPresentationController這個類:
class CustomPresentationController: UIPresentationController, UIViewControllerTransitioningDelegate { var presentationWrappingView: UIView? // 這個視圖封裝了原視圖,添加了暗影和圓角後果 var dimmingView: UIView? = nil // alpha為0.5的黑色蒙版 // 告知UIKit為哪一個視圖添加動畫後果 override func presentedView() -> UIView? { return self.presentationWrappingView } } // 四個辦法自界說轉場動畫產生前後的操作 extension CustomPresentationController { override func presentationTransitionWillBegin() { // 設置presentationWrappingView和dimmingView的UI後果 let transitionCoordinator = self.presentingViewController.transitionCoordinator() self.dimmingView?.alpha = 0 // 經由過程轉場調和器履行同步的動畫後果 transitionCoordinator?.animateAlongsideTransition({ (context: UIViewControllerTransitionCoordinatorContext) -> Void in self.dimmingView?.alpha = 0.5 }, completion: nil) } /// present停止時,把dimmingView和wrappingView都清空,這些暫時視圖用不到了 override func presentationTransitionDidEnd(completed: Bool) { if !completed { self.presentationWrappingView = nil self.dimmingView = nil } } /// dismiss開端時,讓dimmingView完整通明,這個動畫和animator中的動畫同時產生 override func dismissalTransitionWillBegin() { let transitionCoordinator = self.presentingViewController.transitionCoordinator() transitionCoordinator?.animateAlongsideTransition({ (context: UIViewControllerTransitionCoordinatorContext) -> Void in self.dimmingView?.alpha = 0 }, completion: nil) } /// dismiss停止時,把dimmingView和wrappingView都清空,這些暫時視圖用不到了 override func dismissalTransitionDidEnd(completed: Bool) { if completed { self.presentationWrappingView = nil self.dimmingView = nil } } } extension CustomPresentationController { }
除此之外,這個類還要處置子視圖結構相干的邏輯。它作為動畫署理,還須要為動畫供給animator對象,具體代碼請在demo的Custom Presentation文件夾下浏覽。
UINavigationController轉場動畫
到今朝為止,一切轉場動畫都是實用於present和dismiss的,其實UINavigationController也能夠自界說轉場動畫。二者是平行關系,許多都可以類比過去:
class FromViewController: UIViewController, UINavigationControllerDelegate { let toViewController: ToViewController = ToViewController() override func viewDidLoad() { super.viewDidLoad() setupView() // 重要是一些UI控件的結構,可以疏忽其完成細節 self.navigationController.delegate = self } }
與present/dismiss分歧的時,如今視圖掌握器完成的是UINavigationControllerDelegate協定,讓本身成為navigationController的署理。這個協定相似於此前的UIViewControllerTransitioningDelegate協定。
FromViewController完成UINavigationControllerDelegate協定的詳細操作以下:
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,就和此前沒有任何差別了。可見,一個封裝得很好的animator,不只能在present/dismiss時應用,乃至還可以在push/pop時應用。
UINavigationController也能夠添加交互式轉場動畫,道理也和此前相似。
總結
關於非交互式動畫,須要設置presentedViewController的transitioningDelegate屬性,這個署理須要為present和dismiss供給animator。在animator中劃定了動畫的連續時光和表示邏輯。
關於交互式動畫,須要在此前的基本上,由transitioningDelegate屬性供給交互式動畫掌握器。在掌握器中停止事宜處置,然後更新動畫完成進度。
關於自界說動畫,可以經由過程UIPresentationController中的四個函數自界說動畫履行前後的後果,可以修正presentedViewController的年夜小、外不雅並同步履行其他的動畫。
自界說動畫的水照樣比擬深,本文僅合適做入門進修用,迎接相互交換。
【IOS實戰之自界說轉場動畫詳解】的相關資料介紹到這裡,希望對您有所幫助! 提示:不會對讀者因本文所帶來的任何損失負責。如果您支持就請把本站添加至收藏夾哦!