你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> IOS實戰之自界說轉場動畫詳解

IOS實戰之自界說轉場動畫詳解

編輯:IOS開發綜合

轉場動畫這事,說簡略也簡略,可以經由過程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本身便可以作為署理
  • 設置B的transitioningDelegate為步調1中創立的署理對象
  • 挪用presentViewController:animated:completion:並把參數animated設置為true
  • 體系會找到署理中供給的Animator,由Animator擔任動畫邏輯

器具體的例子說明就是:

// 這個類相當於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,唯一的差別在於:

  • present時,要把toView參加到container的視圖層級。
  • dismiss時,要把fromView從container的視圖層級中移除。

假如您被後面這一年夜段代碼和常識弄暈了,或許臨時用不到這些詳細的常識,您至多須要記住自界說動畫的根本道理和流程:

  • 設置將要跳轉到的視圖掌握器(presentedViewController)的transitioningDelegate
  • 充任署理的對象可所以源視圖掌握器(presentingViewController),也能夠是本身創立的對象,它須要為轉場動畫供給一個animator對象。
  • animator對象的animateTransition是全部動畫的焦點邏輯。

交互式(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具有以下幾個功效:

  • 設置presentedViewController的視圖年夜小
  • 添加自界說視圖來轉變presentedView的外不雅
  • 為任何自界說的視圖供給轉場動畫後果
  • 依據size class停止呼應式結構

您可以以為,. FullScreen和其他present作風都是swift為我們完成供給好的,它們是.Custom的特例。而.Custom許可我們加倍自在的界說轉場動畫後果。

UIPresentationController供給了四個函數來界說present和dismiss動畫開端前後的操作:

  • presentationTransitionWillBegin: present將要履行時
  • presentationTransitionDidEnd:present履行停止後
  • dismissalTransitionWillBegin:dismiss將要履行時
  • dismissalTransitionDidEnd: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實戰之自界說轉場動畫詳解】的相關資料介紹到這裡,希望對您有所幫助! 提示:不會對讀者因本文所帶來的任何損失負責。如果您支持就請把本站添加至收藏夾哦!

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved