iOS9-自定義轉場

參考書籍:iOS 9 by Tutorialsgit

博客原文swift

其實早在iOS7就推出了兩個View之間的自定義過分轉變。可是在iOS9中這種自定義轉換進一步讓你經過自定義segues來使過渡動畫和視圖控制器徹底分離。閉包

經過一個小的demo來了解一下吧。app

Getting started

初始化代碼ide

一個簡單的項目PamperedPets,寵物照看的應用程序,完成後講顯示寵物的思想和她們的詳細列表。函數

試着探索一下這個初始項目,看他是怎麼運行的。動畫

注意:當你打開這個項目的時候在Storyboard中會有些警告,不要驚慌。以後會解決的。spa

看一下Main.storyboard它有一些預先建立好的scenes,你將開始Animal Detail and Animal Photo scenes的工做。.net

What are segues?

Segues描述了場景之間的轉換。他們顯示爲視圖控制器場景之間的箭頭,有幾種類型的 Seguescode

  • 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

A simple segue

Main.storyboard裏選擇Animal Detail View Controller,從Object Library拖拽出一個Tap Gesture Recognizer放在Pet Photo Thumbnail上。

接下來,Ctrl-dragTap Gesture Recognizer TO Tap Gesture Recognizer ,從彈出的菜單中選擇present modally

完成上邊的步驟就創建好了一個segue

選擇 Animal Detail View ControllerAnimal 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 modalAnimalDetailViewControllerAnimalPhotoViewControllerAnimalDetailViewController在這裏被稱做爲source view controller,而AnimalPhotoViewController責備稱做爲destination view controller。這個segue持有sourcedestination 的引用。

在這個過程當中,目標視圖控制器將會調用transition delegate來執行默認的Cover Vertical動畫。

Your custom segue library

Main.storyboard中選擇 PhotoDetail segue( the Animal Detail and the Animal Photo view controllers.)改變他的segue class DropSegue

再次運行項目,你會發現點擊照片以後的動畫已經徹底改變了。

Creating a custom segue

如今你建立一個本身定義的的segue去更換DropSegue。而且將要建立一個以下圖的轉場動畫.

建立一個自定義的seuge最難的部分就是術語,你將要使用的協議名字至關的長。

  • UIViewControllerTransitioningDelegate : 自定義轉場使用該協議來完成動畫。

  • UIViewControllerAnimatedTransitioning: 該動畫對象經過該協議來描述動畫。

  • UIViewControllerContextTransitioning: 這個上下文包含有關呈現,並介紹控制器和視圖的詳細信息;你把它傳遞給動畫對象,爲他們提供在其上執行動畫的背景下。

在你開始以前,咱們先看看建立一個轉場的動畫都須要那些步驟:

  1. 繼承UIStoryboardSegue的子類,設置segue 爲目標控制器的委託.

  2. 建立一個展現和消失的animator

  3. 定義動畫效果及其持續時間,在動畫中使用。

  4. 指導segue用於演示和解僱動畫類。

  5. 最後在故事版中使用這個segue

Subclass UIStoryboardSegue

建立一個Cocoa Touch Class文件命名爲ScaleSegue.swift繼承UIStoryboardSegue

而後擴展這個類

extension ScaleSegue: UIViewControllerTransitioningDelegate {
}

ScaleSegue這個類裏重寫父類的方法preform()

override func perform() {
  destinationViewController.transitioningDelegate = self
  super.perform()
}

在這裏你設置destination view controllertransitioning delegateScaleSegue

Create the animator

ScaleSegue.swift文件下邊寫以下一個類:

class ScalePresentAnimator : NSObject,
 UIViewControllerAnimatedTransitioning {
}

你將使用ScalePresentAnimator這個類去展示modal view 。你也將創建一個消失時的動畫,可是目前來講,一切都仍是使用的默認的動畫。須要注意的是Xcode中會抱怨這還不符合UIViewControllerAnimatedTransitioning協議;你只是要解決這個問題。

Define the animation

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)
        }
        
    }

Set the animator in the segue

UIViewControllerTransitioningDelegate下添加下邊這個方法。

extension ScaleSegue:UIViewControllerTransitioningDelegate{
    func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return ScalePresentAnimator()
    }
}
這是簡單的在告訴`segue`在展示下一個view的時候使用你的`ScalePresentAnimator`動畫

Use the segue in the storyboard

接下來在Main.storyboard中將PhotoDetail segue更換成ScaleSegue,同時呢,改變Presentation成爲Form Sheet

接下來運行程序你就會看到下邊的動畫。

Passing data to animators

經過協議來傳遞數據。在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你就會發現當你單擊圖片以後,圖片就會從本來的位置滿滿地放大。是否是這樣子看起來更加的舒服呢?

Working with the view hierarchy

接下來咱們作點小的改變來讓你的app在iPad上運行起來別具一格。

找到animateTransition(_:)這個函數,在` toView?.frame = finalFrame
toView?.layoutIfNeeded()`後邊緊跟着寫上下邊的代碼

fromView?.alpha = 0.0

而後在動畫完成的閉包裏寫上:

fromView?.alpha = 1.0
 transitionContext.completeTransition(true)

接下來在你的iPad中運行你的app,你會看到下邊的樣子。

Handling embedded view controllers

接下來呢咱們在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處理狀況也是相似的。

Completing the scale segue dismissal

你會發現若是再次點擊大圖,大圖消失的時候的動畫仍是以前的默認狀況。咱們接下來就完成消失時的動畫吧。其實呢既然已經完成了presenting animator那麼dismiss animator就簡單了許多了吧。道理是同樣的,那你就挑戰一下本身吧。完成接下來的任務!

修改下邊這個段代碼

if let toView = toView{
            transitionContext.containerView()?.addSubview(toView)
        }

修改成

if let toView = toView,fromView = fromView{
           transitionContext.containerView()?.insertSubview(toView, belowSubview: fromView)
       }
相關文章
相關標籤/搜索