參考書籍:iOS 9 by Tutorials
git
博客原文swift
其實早在iOS7就推出了兩個View之間的自定義過分轉變。可是在iOS9中這種自定義轉換進一步讓你經過自定義segues
來使過渡動畫和視圖控制器徹底分離。閉包
經過一個小的demo來了解一下吧。app
初始化代碼ide
一個簡單的項目PamperedPets
,寵物照看的應用程序,完成後講顯示寵物的思想和她們的詳細列表。函數
試着探索一下這個初始項目,看他是怎麼運行的。動畫
注意:當你打開這個項目的時候在
Storyboard
中會有些警告,不要驚慌。以後會解決的。spa
看一下Main.storyboard
它有一些預先建立好的scenes
,你將開始Animal Detail
and Animal Photo
scenes的工做。.net
Segues
描述了場景之間的轉換。他們顯示爲視圖控制器場景之間的箭頭,有幾種類型的 Segues
code
Show : Pushes a scene from a navigation controller.
Show Detail: Replaces a scene detail when in a UISplitViewController
Present Modally: Presents a scene on top of the current scene.
Popover: Presents a scene as a popover on the iPad or full screen on the iPhone.
這篇文章咱們僅僅自定義modal segues
在Main.storyboard
裏選擇Animal Detail View Controller
,從Object Library
拖拽出一個Tap Gesture Recognizer
放在Pet Photo Thumbnail
上。
接下來,Ctrl-drag
從Tap Gesture Recognizer
TO Tap Gesture Recognizer
,從彈出的菜單中選擇present modally
完成上邊的步驟就創建好了一個segue
。
選擇 Animal Detail View Controller
和Animal Photo View Controller
之間的segue
.獎identifier
設置爲PhotoDetail
在AnimalDetailViewController.swift
中重寫prepareForSegue(_:sender:)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "PhotoDetail" { let controller = segue.destinationViewController as! AnimalPhotoViewController controller.image = imageView.image } }
如今你運行app而且點擊照片,你將會看到一個大的圖片出現。
你會發現你回不去了。那麼此時你須要建立一個unwind segue
。在AnimalDetailViewController.swift:
中添加以下代碼:
@IBAction func unwindToAnimalDetailViewController( segue:UIStoryboardSegue) { // placeholder for unwind segue }
對於一個簡單的返場,在這個方法裏你不須要添加任何的代碼。 任何相似於這樣的方法 @IBAction func methodName(segue:UIStoryboardSegue)
都會被認爲是Storyboard segue 的 unwind
在Main.storyboard
中選擇Animal Photo View Controller scene.
。從Object Library
拖出來一個Tap Gesture Recognizer
放在Pet Photo View
上。接下來,Ctrl-drag
從你的Tap Gesture Recognizer
TO Exit
,以後從彈出的菜單中選擇unwindToAnimalDetailViewController
從新運行app,就會回發現你從大的圖片中返回去了。
咱們來探究一下這裏發生了什麼。當你點擊詳細視圖中的縮略圖的時候,手勢識別就開始一個segue modal
從AnimalDetailViewController
到AnimalPhotoViewController
。AnimalDetailViewController
在這裏被稱做爲source view controller
,而AnimalPhotoViewController
責備稱做爲destination view controller
。這個segue
持有source
和destination
的引用。
在這個過程當中,目標視圖控制器將會調用transition delegate
來執行默認的Cover Vertical
動畫。
在Main.storyboard
中選擇 PhotoDetail segue
( the Animal Detail and the Animal Photo view controllers.
)改變他的segue class
爲DropSegue
再次運行項目,你會發現點擊照片以後的動畫已經徹底改變了。
如今你建立一個本身定義的的segue
去更換DropSegue
。而且將要建立一個以下圖的轉場動畫.
建立一個自定義的seuge最難的部分就是術語,你將要使用的協議名字至關的長。
UIViewControllerTransitioningDelegate : 自定義轉場使用該協議來完成動畫。
UIViewControllerAnimatedTransitioning: 該動畫對象經過該協議來描述動畫。
UIViewControllerContextTransitioning: 這個上下文包含有關呈現,並介紹控制器和視圖的詳細信息;你把它傳遞給動畫對象,爲他們提供在其上執行動畫的背景下。
在你開始以前,咱們先看看建立一個轉場的動畫都須要那些步驟:
繼承UIStoryboardSegue
的子類,設置segue
爲目標控制器的委託.
建立一個展現和消失的animator
類
定義動畫效果及其持續時間,在動畫中使用。
指導segue
用於演示和解僱動畫類。
最後在故事版中使用這個segue
建立一個Cocoa Touch Class
文件命名爲ScaleSegue.swift
繼承UIStoryboardSegue
。
而後擴展這個類
extension ScaleSegue: UIViewControllerTransitioningDelegate { }
在ScaleSegue
這個類裏重寫父類的方法preform()
override func perform() { destinationViewController.transitioningDelegate = self super.perform() }
在這裏你設置destination view controller
的transitioning delegate
是 ScaleSegue
。
在ScaleSegue.swift
文件下邊寫以下一個類:
class ScalePresentAnimator : NSObject, UIViewControllerAnimatedTransitioning { }
你將使用ScalePresentAnimator
這個類去展示modal view
。你也將創建一個消失時的動畫,可是目前來講,一切都仍是使用的默認的動畫。須要注意的是Xcode中會抱怨這還不符合UIViewControllerAnimatedTransitioning
協議;你只是要解決這個問題。
ScalePresentAnimator
聽從UIViewControllerAnimatedTransitioning
,你須要實現這個協議所必需的一些方法。
func transitionDuration( transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 2.0 } //規定動畫持續的時間(通常時間比較短,這裏設置的比較長,是爲了明顯的看到效果)
實際的動畫效果:
func animateTransition(transitionContext: UIViewControllerContextTransitioning){ // 1 獲取到目標視圖的控制器和View let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let toView = transitionContext.viewForKey(UITransitionContextFromViewKey) // 2 添加 toView到transiton的context if let toView = toView{ transitionContext.containerView()?.addSubview(toView) } //3 目標視圖的初始狀態是在屏幕左上角大小爲零的一個矩形,當你更改視圖的 Frame 時老是要去調用`layoutIfNeeded`來更新視圖的約束 toView?.frame = .zero toView?.layoutIfNeeded() //4 這個動畫必報就是把那個大小爲零的矩形View變成最終的大小的一個動畫 let duration = transitionDuration(transitionContext) let finalFrame = transitionContext.finalFrameForViewController(toViewController) UIView.animateWithDuration(duration, animations: { () -> Void in toView?.frame = finalFrame toView?.layoutIfNeeded() }) { (_) -> Void in //5 transitionContext要在動畫結束時清理,調用completeTransition transitionContext.completeTransition(true) } }
在UIViewControllerTransitioningDelegate
下添加下邊這個方法。
extension ScaleSegue:UIViewControllerTransitioningDelegate{ func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return ScalePresentAnimator() } } 這是簡單的在告訴`segue`在展示下一個view的時候使用你的`ScalePresentAnimator`動畫
接下來在Main.storyboard
中將PhotoDetail segue
更換成ScaleSegue
,同時呢,改變Presentation
成爲Form Sheet
接下來運行程序你就會看到下邊的動畫。
經過協議來傳遞數據。在ScaleSegue.swift
裏創建一個 protocol
protocol ViewScaleable{ var scaleView:UIView{get} }
經過擴展AniamalDetailViewController
繼承ViewScaleable
協議
在AniamalDetailViewController.swift
中添加下邊的代碼
extension AniamalDetailViewController:ViewScaleable{ var scaleView: UIView {return imageView} }
在ScaleSegue.swift
文件中找到animateTransiton
這個函數,在let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
下添加以下代碼
//獲取源視圖的控制器和View let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)
將toView?.frame = .zero
替換爲
var startFrame = CGRect.zero if let fromViewController = fromViewController as? ViewScaleable{ startFrame = fromViewController.scaleView.frame }else{ print("Warning: Controller \(fromViewController) does not"+"conform to ViewScaleable") } toView?.frame = startFrame
如今你從新運行你的app你就會發現當你單擊圖片以後,圖片就會從本來的位置滿滿地放大。是否是這樣子看起來更加的舒服呢?
接下來咱們作點小的改變來讓你的app在iPad上運行起來別具一格。
找到animateTransition(_:)
這個函數,在` toView?.frame = finalFrame
toView?.layoutIfNeeded()`後邊緊跟着寫上下邊的代碼
fromView?.alpha = 0.0
而後在動畫完成的閉包裏寫上:
fromView?.alpha = 1.0 transitionContext.completeTransition(true)
接下來在你的iPad中運行你的app,你會看到下邊的樣子。
接下來呢咱們在Main.storyboard
中選擇 Navigation Controller
,在屬性面板中勾選上Is Initial View Controller:
這一項。
如今呢你運行程序你會首先看到一個動物的列表,你任意的點擊一個,而後點擊圖片你會發現。奇怪怎麼又變成了從左上角出現的動畫了。
那是應爲我把視圖控制器如今嵌入了導航控制器裏,使得呈現視圖控制器的不是AnimalDetailViewController
那很簡單咱們來解決一下就行了。
咱們在ScaleSegue.swift
這個文件裏找到,
let fromViewController = transitionContext .viewControllerForKey( UITransitionContextFromViewControllerKey)!
將這句代碼改成:
var fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! if let fromNC = fromViewController as? UINavigationController{ if let controller = fromNC.topViewController{ fromViewController = controller } }
此刻你從新運行代碼就會恢復原樣嘍。
當你嵌入了一個UITaBarController
處理狀況也是相似的。
你會發現若是再次點擊大圖,大圖消失的時候的動畫仍是以前的默認狀況。咱們接下來就完成消失時的動畫吧。其實呢既然已經完成了presenting animator
那麼dismiss animator
就簡單了許多了吧。道理是同樣的,那你就挑戰一下本身吧。完成接下來的任務!
修改下邊這個段代碼
if let toView = toView{ transitionContext.containerView()?.addSubview(toView) }
修改成
if let toView = toView,fromView = fromView{ transitionContext.containerView()?.insertSubview(toView, belowSubview: fromView) }