用一個很簡單的場景做為例子:在storyboard上,你有用UINavigationController串起來兩個UIViewController。這兩個controller之間要互相跳轉(zhuǎn),A->B, B->A。跳轉(zhuǎn)的時(shí)候默認(rèn)的那個push來push去的效果你覺得很傻X,所以想換一個效果。比如,不那么二的fade in/out效果。
很多的例子會說寫一個cusom的UIStoryboardSegue,然后在這個里面寫一個UIView.animationWithDuration來實(shí)現(xiàn)這個效果。千萬別這么干!從iOS7開始就有一個更加方便簡潔的方法可以實(shí)現(xiàn)這個效果。
下面就開始介紹這個很牛X的方法。首先創(chuàng)建一個single view的項(xiàng)目。名稱可以叫做transitionDemo。各個controller之間的關(guān)系是這樣的:

一個
UINavigationController作為啟動controller,串起來一個UITableViewController(root controller)和一個UIViewController。在Prototype Cells上ctrl+drag到view controller上,并選擇show。
下面分別為table view controller創(chuàng)建類TDTableViewController為view controller創(chuàng)建類TDViewController之后分別在storyboard里關(guān)聯(lián)起來。把從table view controller到view controller的segue的identitifer設(shè)置為TDViewController。

接下來給TDTableViewController添加數(shù)據(jù)源:
class TDTableViewController: UITableViewController {
var tableViewDataSource: [String]?
override func viewDidLoad() {
super.viewDidLoad()
createDataSource()
}
func createDataSource() {
if let _ = self.tableViewDataSource {
return
}
self.tableViewDataSource = [String]()
for i in 0..<100 {
self.tableViewDataSource!.append("item :- [\(i)]")
}
}
//............
}
打開storyboard,在TDViewController里添加一個label,給這個label添加約束,隨便是什么約束都可以只要是正確的。然后在controller里添加這個label的outlet并關(guān)聯(lián)。
在TDViewController代碼中添加數(shù)據(jù)屬性,并在viewDidLoad方法里給這label的text賦值:
class TDViewController: UIViewController {
@IBOutlet weak var dataLabel: UILabel!
var singleData: String!
override func viewDidLoad() {
super.viewDidLoad()
self.dataLabel.text = self.singleData
}
}
使用默認(rèn)的segue,這里現(xiàn)在是show模式,跳轉(zhuǎn)。并從table view controller出傳遞數(shù)據(jù)給view controller:
// 使用segue跳轉(zhuǎn)
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.dataItem = self.tableViewDataSource![indexPath.row]
self.performSegueWithIdentifier("TDViewController", sender: nil)
}
// 傳遞數(shù)據(jù)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier != "TDViewController" {
return
}
let destinationController = segue.destinationViewController as! TDViewController
destinationController.singleData = self.dataItem!
}
run一把來看看:

Segue和Transition
custom segue
直接用代碼把兩種方式都實(shí)現(xiàn)出來,分別代替上面使用的默認(rèn)的實(shí)現(xiàn)方式。
首先弄一個custom segue。要開發(fā)一個custom segue,首先需要創(chuàng)建一個類并繼承自UIStoryboardSegue,我們?yōu)檫@個類命名為DetailStoryboardSegue。在這個類中關(guān)鍵就是實(shí)現(xiàn)方法perform()。
import UIKit
class DetailStoryboardSegue: UIStoryboardSegue {
override func perform() {
let sourceView = self.sourceViewController.view // 1
let destView = self.destinationViewController.view
let window = (UIApplication.sharedApplication().delegate as! AppDelegate).window
window?.insertSubview(destView, aboveSubview: sourceView) // 2
destView.alpha = 0.0
UIView.animateWithDuration(0.3, animations: { // 3
destView.alpha = 1.0
})
}
}
-
self.sourceViewController和self.destinationViewController都是UIStoryboardSegue類本身就有的屬性。從A controller跳轉(zhuǎn)到B controller,那么source就是A,destination就是B。我們的fade in/out效果就是通過source和destination controller的view的切換和alpha實(shí)現(xiàn)的。 - 在window上做source和destination view的切換。把destination view覆蓋到source view上。
- destination view的alpha在上一步設(shè)置為了0,也就是完全透明的。在動畫中把destination view的alpha設(shè)置回完全不透明,把view呈現(xiàn)在用戶面前達(dá)到fade in的效果。
實(shí)現(xiàn)完成后,在storyboard中把segue的Kind 設(shè)置為custom,然后給Class設(shè)置為類DetailStoryboardSegue。

其實(shí)很簡單,運(yùn)行起來看看。

你會看到,運(yùn)行的結(jié)果出了一點(diǎn)問題。之前在正確位置顯示的label,在這里居然出現(xiàn)在了屏幕的頂端。這是因?yàn)榍懊?code>TDViewController是用navigation controller的push出來的,屏幕的最頂端有一個navigation bar,所以label的top約束是有效的。而我們的custom segue的切換中并不存在navigation controller的push。而是簡單的view的覆蓋替換,沒有navigation bar。所以labe的top約束失效了,直接被顯示在了頂端。
這個錯誤其實(shí)引出了一個問題,但是這里我們暫時(shí)不做深入討論。先看看Transitioning animtion動畫是怎么運(yùn)行的,然后我們討論這個嚴(yán)肅的問題。
custom transitioning animation
實(shí)現(xiàn)自定義的切換動畫就需要實(shí)現(xiàn)UIViewControllerAnimatedTransitioning這個protocol了。我們自定義一個類DetailTransitioningAnimator來實(shí)現(xiàn)這個protocol。
這個protocol有兩個方法是必須實(shí)現(xiàn)的,func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval和func animateTransition(transitionContext: UIViewControllerContextTransitioning)。來看代碼:
import UIKit
class DetailTransitioningAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let durationTimeInterval: NSTimeInterval
// 1
init(duration: NSTimeInterval){
durationTimeInterval = duration
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return self.durationTimeInterval
}
//2
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView()
let sourceController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
let destController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
let sourceView = sourceController!.view
let destView = destController!.view
containerView?.addSubview(destView) // 3
destView.alpha = 0.0
UIView.animateWithDuration(0.3, animations: {
destView.alpha = 1.0
}, completion: {completed in
let cancelled = transitionContext.transitionWasCancelled()
transitionContext.completeTransition(!cancelled)
})
}
func animationEnded(transitionCompleted: Bool) {
}
}
- 這個
init方法不是必需的,但是為了可以自定義動畫的執(zhí)行時(shí)間添加這個構(gòu)造方法。 - transitioning動畫的執(zhí)行方法。在這個方法里實(shí)現(xiàn)我們在之前的custom segue里實(shí)現(xiàn)的效果。
- 注意這里,用的是
let containerView = transitionContext.containerView()得到的container view。而不是之前用到的window。
下面把transitioning動畫應(yīng)用到代碼中。在使用的時(shí)候需要實(shí)現(xiàn)UINavigationControllerDelegate。創(chuàng)建一個類NavigationControllerDelegate來實(shí)現(xiàn)這個protocol。
import UIKit
class NavigationControllerDelegate: NSObject, UINavigationControllerDelegate {
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return DetailTransitioningAnimator(duration: 0.3)
}
}
這里沒有太多需要講的。只要知道是這個方法就可以了。
下面在storyboard中把類NavigationControllerDelegate應(yīng)用在UINavigationController中。
- 把object這個東西拖動到項(xiàng)目中唯一存在的navigation controller上。
- 給剛剛的object設(shè)置custom class為類
NavigationControllerDelegate。
只要關(guān)注右側(cè)的Class的設(shè)置就可以。 -
給navigation controller的代理設(shè)置為剛剛拖動上去的delegate。
對上面的custom segue代碼去掉,并稍作修改之后,運(yùn)行起來。

木有問題,上面出現(xiàn)的錯誤也沒有了。
這個問題不重要,但是還是要討論一下:為什么custom segue會出現(xiàn)問題,而transitoning動畫就沒有問題呢?這里是因?yàn)閷?shí)際上transitioning動畫是在navigation controller的基礎(chǔ)上改變的。Transitioning動畫只是修改了navigation controller的push動畫,改成了fade in的效果,而navigation controller的其他機(jī)制沒有改動。custom segue則完全是兩個view之間的動畫。那么,這里就留下一個問題由讀者去修改上面的custom segue代碼來讓這個navigation bar 出現(xiàn)出來。
但是什么情況下用custom segue,什么情況下用transition動畫呢?Transitioning動畫更加的靈活,不像custom segue是在storyboard里定死的。你可以根據(jù)不同的情況設(shè)定你自己想要的動畫。
custom segue就是用來調(diào)用一些如:presentViewController和dismissViewController之類的方法的。這些方法調(diào)用的時(shí)候兩個view controller之間的轉(zhuǎn)化動畫則使用上面的方法來做。他們的職責(zé)是不同的。
最后補(bǔ)充一點(diǎn)。也是一個發(fā)揮custom segue的作用的地方。在多個storyboard的情況下可以使用custom segue。步驟:(假設(shè)你已經(jīng)創(chuàng)建了另外一個storyboard,叫做Another.storyboard)
- 拖一個storyboard reference到你現(xiàn)在的storyboard中。
- 點(diǎn)選這個storyboard reference,并在右側(cè)工具欄的Storyboard下填寫另外一個storyboard的名字,這里是Another.storyboard。
- ctrl+drag,從一個view controller中的按鈕等view連接到剛剛添加的storyboard reference上。(確保你的另外一個storyboard的view controller已經(jīng)設(shè)置為默認(rèn)啟動)
- 如果你要啟動的是另外一個storyboard的某一個特定的view controller,那么就可以寫一個custom segue了。在這個custom segue中初始化出另外一個storyboard,從中獲取到你要啟動的view controller,然后在custom segue的perform方法中完成controller跳轉(zhuǎn)的最后一步。
all code here


