自定義ViewController的轉場動畫 (Swift)
Presentation ViewController
基礎知識
在沒有 UINavigationController
的時候,我們通常用 present modally
(彈出模態控制器)的方式切換檢視。預設情況下,目標檢視從螢幕的下方彈出。具體方法是:
通過 presentViewController(_: animated:completion:)
來彈出檢視,
通過 viewController
的 modalTransitionStyle
的屬性設定彈出 ViewController
時的動畫:
viewController.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve presentViewController(secondViewController, animated: true, completion: nil)
系統自帶的四種動畫有:
enum UIModalTransitionStyle: Int { case CoverVertical// 底部滑入,預設 case FlipHorizontal// 水平翻轉 case CrossDissolve// 隱出隱現 case PartialCurl// 翻頁 }
自定義轉場動畫
在轉場的過程中系統會提供一個檢視容器 containerView
,當前的檢視(fromView)會自動加入到這個容器中,我們所要做的就是將目標檢視(toView)加入到這個容器中,然後為目標檢視的展現增加動畫。

需要注意的是:如果是從 A 檢視控制器 present 到 B,則A是fromView,B是toView。從 B 檢視控制器dismiss到 A 時,B 變成了fromView,A是toView。
建立動畫管理器類,該類需繼承NSObject並遵循UIViewControllerAnimatedTransitioning協議:
import UIKit class FadeAnimator: NSObject, UIViewControllerAnimatedTransitioning { let duration = 1.0 // 指定轉場動畫持續的時間 func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval { return duration } // 實現轉場動畫的具體內容 func animateTransition(transitionContext: UIViewControllerContextTransitioning) { // 得到容器檢視 let containerView = transitionContext.containerView() // 目標檢視 let toView = transitionContext.viewForKey(UITransitionContextToViewKey)! containerView.addSubview(toView) // 為目標檢視的展現新增動畫 toView.alpha = 0.0 UIView.animateWithDuration(duration, animations: { toView.alpha = 1.0 }, completion: { _ in transitionContext.completeTransition(true) }) } }
然後在ViewController(第一個VC)中加入如下程式碼:
// 宣告一個動畫例項 let transition = FadeAnimator()
// 遵循 UIViewControllerTransitioningDelegate 協議,並實現其中的兩個方法:
extension ViewController: UIViewControllerTransitioningDelegate { // 提供彈出時的動畫 func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return transition } // 提供消失時的動畫 func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return transition } }
@IBAction func register(sender: AnyObject) { var viewController = storyboard!.instantiateViewControllerWithIdentifier("SecondViewController") as SecondViewController viewController.transitioningDelegate = self presentViewController(viewController, animated: true, completion: nil) }
最終效果如下:

效果展示
Segue
通過Segue轉場,即通過在Storyboard中拖線的方式進行轉場。自定義轉場動畫的關鍵就是:
- 建立一個UIStoryboardSegue的子類,並重載其中perform方法。
- 在perform方法中實現自定義動畫的邏輯。
- 將這個UIStoryboardSegue類與拖的segue線進行繫結。
下面我們一步步來實現:
建立兩個UIViewController類——FirstViewController、SecondViewController,分別對應Storyboard中的兩個ViewController場景,從第一個VC向第二個VC拖一個Segue,並選擇custom:

Storyboard.png
建立一個UIStoryboardSegue子類,並與上一部的Segue進行繫結,並指定它的Identifier:

Segue.png
開啟FirstCustomSegue.swift,在perform方法中實現具體動畫:
import UIKit class FirstCustomSegue: UIStoryboardSegue { // 過載perform方法,在這裡我們新增想要的自定義邏輯 override func perform() { // 得到源控制器和目標控制器的檢視 var sourceView = self.sourceViewController.view as UIView! var destinationView = self.destinationViewController.view as UIView! // 得到螢幕的寬和高 let screenWidth = UIScreen.mainScreen().bounds.size.width let screenHeight = UIScreen.mainScreen().bounds.size.height // 把destinationView放在sourceView的下面 destinationView.frame = CGRectMake(0.0, screenHeight, screenWidth, screenHeight) let window = UIApplication.sharedApplication().keyWindow // 把目標檢視新增到當前檢視中 window?.insertSubview(destinationView, aboveSubview: sourceView) UIView.animateWithDuration(0.4, animations: { () -> Void in sourceView.frame = CGRectOffset(sourceView.frame, 0.0, -screenHeight) destinationView.frame = CGRectOffset(destinationView.frame, 0.0, -screenHeight) }, completion: { _ in // 展示新的檢視控制器 self.sourceViewController.presentViewController(self.destinationViewController as UIViewController, animated: false, completion: nil) }) } }
在FirstViewController中執行這個自定義轉場,這裡我們採用滑動手勢:
override func viewDidLoad() { super.viewDidLoad() var swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "showSecondViewController") swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Up self.view.addGestureRecognizer(swipeGestureRecognizer) } func showSecondViewController() { self.performSegueWithIdentifier("FirstCustomSegue", sender: self) }
解除轉場
與轉場對應的是解除轉場(unwind segue),和正常轉場一樣,也需要建立一個UIStoryboardSegue的子類,並重載其中perform方法。
不同的是,解除轉場需要在第一個ViewController中建立一個帶UIStoryboardSegue類引數的IBAction方法,這個方法的內容可以為空,但必須存在。
需要注意的是,FirstViewController沒有UINavigationController的時候解除轉場的動畫才會出現,但是正常轉場效果是都有的,不知道這是不是個漏洞,知道的老師可以告訴下,謝謝。
下面我們來實現這個自定義解除轉場:
開啟FirstViewController.swift,新增程式碼:
@IBAction func backFromSegueAction(sender: UIStoryboardSegue) { }
開啟Storyboard,在SecondViewController場景中進行如下操作:

1.png
然後在Scene中會出現Unwind segue,選中後在屬性中設定Identifier:backFromSegue

222.png
建立UIStoryboardSegue子類-FirstCustomSegueUnwind,重寫其中的perform方法:
import UIKit class FirstCustomSegueUnwind: UIStoryboardSegue { override func perform() { var secondVCView = self.sourceViewController.view as UIView! var firstVCView = self.destinationViewController.view as UIView! let screenHeight = UIScreen.mainScreen().bounds.size.height let window = UIApplication.sharedApplication().keyWindow window?.insertSubview(firstVCView, aboveSubview: secondVCView) UIView.animateWithDuration(0.4, animations: { () -> Void in firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, screenHeight) secondVCView.frame = CGRectOffset(secondVCView.frame, 0.0, screenHeight) }) { (Finished) -> Void in self.sourceViewController.dismissViewControllerAnimated(false, completion: nil) } } }
接下來在FirstViewController中來呼叫這個子類,開啟FirstViewController.swift,重寫以下方法:
override func segueForUnwindingToViewController(toViewController: UIViewController, fromViewController: UIViewController, identifier: String?) -> UIStoryboardSegue { if let id = identifier { if id == "backFromSegue" { let unwindSegue = FirstCustomSegueUnwind(identifier: id, source: fromViewController, destination: toViewController, performHandler: { () -> Void in }) return unwindSegue } } return super.segueForUnwindingToViewController(toViewController, fromViewController: fromViewController, identifier: identifier) }
最後,在SecondViewController中執行這個解除轉場,還是採用滑動手勢:
import UIKit class SecondViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() var swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "showFirstViewController") swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Down self.view.addGestureRecognizer(swipeGestureRecognizer) } func showFirstViewController() { self.performSegueWithIdentifier("backFromSegue", sender: self) } }
到這裡,整個轉場就全部寫完了,最後看下效果:

最終效果