iOS自定義過渡動畫

歷時5天從各種英文教程中學習到的過渡動畫,是一個很難忘的探索經歷

比較好的參考文章自定義UIViewController過渡入門動畫入門

轉場方式

首先讓我們來了解iOS轉場的方式:

  • UINavigationController push/pop UIViewController導航欄的轉場
  • UITabBarController 切換Tab的轉場
  • present/dismiss 模態的方式轉場

這是iOS提供的3種基本轉場方式,默認的轉場方式轉場風格有限。例如模態轉場中,儘管有modalPresentationStylemodalTransitionStyle關於展現風格和過渡風格的設置,但是轉場仍是死板從底部彈出。這並不能滿足我們在軟件開發的需求,側邊欄、頂部欄的動畫效果都無法很好地實現。在iOS 7.0之後Apple提供一套完整的自定義過渡動畫的API,爲各種轉場動畫的實現帶了無限可能。

這裏主要介紹模態轉場的自定義動畫。
頂部欄的動畫效果:

SlideMune 的源碼

Modal 轉場

模態轉場分爲非交互式轉場交互式轉場
非交互式轉場也就是普通轉場,轉場的動畫無法交互,不能在動畫的過程中終止轉場。
交互式轉場能通過手指觸摸屏幕通過滑動體驗過渡動畫的進行,並能終止動畫過程。

在這裏插入圖片描述

過渡動畫API

我們定義:如果視圖控制器Apresent之後展示視圖控制器B。在後文中,源視圖控制器fromVC目標視圖控制器toVC

狀態 視圖控制器A 視圖控制器B
Present 源視圖控制器 目標視圖控制器
Dissmiss 目標視圖控制器 源視圖控制器

在這裏插入圖片描述

transitioningDelegate 過渡代理

每個視圖控制器UIViewController都有一個transitioningDelegate屬性,該代理需遵循UIViewControllerTransitioningDelegate協議,提供相關動畫控制器。

每當您顯示或關閉視圖控制器時,UIKit都會要求其過渡代理使用動畫控制器。要將默認動畫替換爲您自己的自定義動畫,必須實現過渡代理,並使其返回適當的動畫控制器。

AnimationController 動畫控制器

過渡代理在present/dismiss時返回相應的動畫控制器。動畫控制器是過渡動畫的核心。它完成了動畫過渡的“繁重工作”。

TransitioningContext 過渡語境

過渡語境在過渡過程中實現並起着至關重要的作用:它封裝了有關過渡中涉及的視圖和視圖控制器的信息。過渡語境輔助動畫控制器實現動畫。
從圖中可以看出,自己並沒有實現此協議。UIKit會爲您創建和配置過渡上下文,並在每次發生過渡時將其傳遞給動畫控制器。

非交互式過渡動畫的過程

以present過渡動畫爲例:

  1. 通過代碼或StoryBoard segue觸發模態視圖present過程。
  2. UIKit向toVC(目標視圖控制器)請求其過渡代理。如果沒有,UIKIt將使用標準的內置過渡。
  3. 然後,UIKit通過來向過渡代理請求動畫控制器animationController(forPresented:presenting:source:)。如果返回nil,則過渡將使用默認動畫。
  4. UIKit構造過渡語境。UIKit通過調用向動畫控制器詢問動畫的持續時間transitionDuration(using:)。UIKit animateTransition(using:)在動畫控制器上調用以執行過渡的動畫。
  5. 最後,動畫控制器調用completeTransition(_:)過渡上下文以指示動畫已完成。

dimiss過渡動畫的步驟幾乎相同。
在這種情況下,UIKit向fromVC視圖控制器(正在關閉的視圖控制器)請求其過渡代理,要求提供動畫控制器animationController(forDismissed:)

非交互式過渡動畫需要提供的條件

  1. 設置過渡動畫代理。設置(目標視圖控制器)的transitioningDelegate屬性,即設置過渡動畫代理對象,該代理對象遵循UIViewControllerTransitioningDelegate協議,實現forPresentedforDismissed兩個方法,分別提供present和dismiss的視圖控制器實例。
  2. 創建動畫控制器。創建present和dismiss的動畫控制器,實現動畫持續時間和構造動畫方法。

這裏我們完成一個簡單的從左到右的過渡動畫。

1. 構建present/dimiss場景

這裏不再講述構建過程,無論是stroyboard還是純代碼都是很好完成的。這裏爲了方便,使用storyboard完成。
左視圖控制器爲:ViewController
右視圖控制器爲:LeftViewController

2. 創建Animation Controller

Animation Controller是自定義過渡動畫的核心對象
AnimationControlle繼承至NSObject,遵循UIViewControllerAnimatedTransitioning協議,實現兩個required方法。

class AnimationController: NSObject, UIViewControllerAnimatedTransitioning {

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        //要求提供動畫師對象的動畫持續時間屬性
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        //過渡動畫的實現效果,也是自定義過渡動畫的核心方法,需要構建動畫的實現。
    }
}

這裏貼出Present狀態下的AnimationController的代碼,Dismiss狀態與其類似(動畫過程執行反過程)。

import UIKit

class PresentAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.6
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        // 1 
        guard let _ = transitionContext.viewController(forKey: .from),
            let toVC = transitionContext.viewController(forKey: .to) else {
                return
        }
        
        // 2
        let containerView = transitionContext.containerView
        containerView.addSubview(toVC.view)
        let duration = transitionDuration(using: transitionContext)
        toVC.view.frame = CGRect(x: -ScreenWidth, y: 0, width: ScreenWidth, height: ScrennHeight)
        
        // 3
        UIView.animate(withDuration: duration, animations: {
            toVC.view.frame = CGRect(x: 0, y: 0, width: ScreenWidth, height: ScrennHeight)
        }) { (_) in
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
        
    }
    
}

在第一個transitionDuration(using:)方法中設置動畫的持續時間。

在第二個transitionDuration(using:)方法中構建動畫。

  1. 獲取過渡動畫所需的視圖控制器及snapshot。從過渡語境中transitionContext.viewController我們可以獲取源視圖控制器fromVC目標視圖控制器toVC,過渡語境封裝了設計的視圖控制器的信息,極大地幫助我們處理視圖控制器的動畫轉化。還可以獲取fromVC和toVC的snapshot(屏幕快照),來構造更加複雜和優秀的動畫。
  2. 管理過渡語境的容器視圖 — containerView和視圖動畫的位置初始化。UIKit將整個過渡封裝在容器視圖中,以簡化視圖層次結構和動畫的管理,容器視圖負責管理fromVC.viewtoVC.view。由UIKit創建的容器視圖僅包含fromVC視圖。您必須添加任何其他將參與過渡的視圖。

addSubview(_:)將新視圖置於視圖層次結構中的所有其他視圖之前,因此添加子視圖的順序很重要。

  1. 設置動畫效果。動畫有兩種實現方法,一種是基礎動畫animate,另一種是關鍵幀動畫animateKeyframes。這裏只是簡單地將fromVC.view從屏幕的左邊界外移動到屏幕中。注意:在動畫完成後需要調用completeTransition(_:)通知UIKit動畫已完成。這將確保最終狀態是一致的。

3.設置過渡動畫代理

設置(目標視圖控制器)destinationVC的modalPresentationStyle爲枚舉屬性custom,過渡動畫的代理爲self即(源視圖控制器)ViewController。

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let destinationVC = segue.destination as? LeftViewController {
            destinationVC.modalPresentationStyle = .custom
            destinationVC.transitioningDelegate = self
        }
    }

然後在(源視圖控制器)添加擴展,遵循UIViewControllerTransitioningDelegate協議,實現forPresentedforDismissed方法。

extension ViewController: UIViewControllerTransitioningDelegate {
    
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        //目標VC是presented,源VC是presenting
        guard let _ = presented as? LeftViewController, let _ = presenting as? ViewController else {
            return nil
        }
        return PresentAnimationController()
    }
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        guard let _ = dismissed as? LeftViewController else {
            return nil
        }
        return DismissAnimationController()
    }
}

這樣就構建好了一個簡單、基礎的自定義轉場過渡動畫。

交互式過渡動畫

交互式動畫會使使用者的動畫體驗更自然舒適而不顯得尖銳,給控制動畫過程留下了餘地。iOS原生APP設置中便有這樣的交互式動畫的例子。

過渡動畫的進度跟隨手指的滑動來啓動/終止轉場動畫,這樣可以帶來良好的用戶交互體驗。

交互式控制器的工作方式

交互控制器通過加快,減慢甚至反轉過渡的過程來響應觸摸事件或編程輸入。爲了啓用交互式轉換,轉換代理必須提供一個交互控制器。您已經制作了過渡動畫。在過渡動畫的基礎上,Apple將交互式動畫封裝成交互控制器將響應手勢來管理此動畫,而不是讓其像視頻一樣播放。Apple提供了現成的UIPercentDrivenInteractiveTransition類,通過繼承該類來創建自己的交互式控制器。

1. 創建交互式控制器

我們在之前過渡動畫的基礎上構建交互式過渡動畫,我們首先需要創建交互式控制器。這裏貼出顯示交互式控制器PresentInteractionController的代碼,繼承封裝好的UIPercentDrivenInteractiveTransition類。

class PresentInteractionController: UIPercentDrivenInteractiveTransition {
    var interactionInProgress = false
    private var shouldCompleteTrantision = false
    private weak var viewController: UIViewController!
    private weak var toViewController: UIViewController!
    
    init(viewController: UIViewController, toViewController: UIViewController) {
        super.init()
        self.viewController = viewController
        self.toViewController = toViewController
        prepareGestureRecognizer(in: self.viewController.view)
    }
    
    private func prepareGestureRecognizer(in view: UIView) {
        let gesture = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleGsture(_:)))
        gesture.edges = .left
        view.addGestureRecognizer(gesture)
    }
    
    @objc func handleGsture(_ gestureRecognizer: UIScreenEdgePanGestureRecognizer) {
        let translation = gestureRecognizer.translation(in: gestureRecognizer.view?.superview)
        var progress = translation.x / 200
        progress = CGFloat(fminf(fmaxf(Float(progress), 0.0), 1.0))
        
        switch gestureRecognizer.state {
        case .began:
            interactionInProgress = true
            viewController.present(toViewController, animated: true, completion: nil)
        case .changed:
            shouldCompleteTrantision = progress > 0.5
            update(progress)
        case .cancelled:
            interactionInProgress = false
            cancel()
        case .ended:
            interactionInProgress = false
            if shouldCompleteTrantision {
                finish()
            } else {
                cancel()
            }
        default:
            break
        }
    }
}
  • interactionInProgressBool屬性,表示交互式場景是否在發生。
  • shouldCompleteTrantisionBool屬性,表示是否應該終止過渡動畫。用於內部管理過渡。
  • viewControllertoViewController,獲取源視圖控制器和目標視圖控制器的引用,用於管理過渡某狀態present視圖控制器,達到交互式控制器與動畫控制器相聯繫的作用。
  • prepareGestureRecognizer(in:)爲源視圖添加屏幕手勢的方法,這裏的交互式動畫爲從左往右present出VC,所以爲源視圖添加屏幕相應在.left的手勢。
  • handleGsture(_:)爲相應手勢變化從而改變過渡動畫狀態的方法。通過聲明局部變量以跟蹤滑動進度translation,根據translation在視圖中獲取並計算過渡進度progress。手勢開始時,您將設置interactionInProgresstrue並觸發prsent視圖控制器。手勢移動時,調用update(_:)更新過渡進度。這是一種根據UIPercentDrivenInteractiveTransition您傳入的百分比移動過渡的方法。如果取消手勢,則更新interactionInProgress並取消過渡。一旦動作已經結束,您使用的過渡進度來決定cancel()finish()

2. 控制器聯繫

(源)視圖控制器與交互控制器相聯

viewController中添加以下屬性:

var presentInteractionController: PresentInteractionController?

並在viewDidLoad()方法中初始化屬性:

presentInteractionController = PresentInteractionController(viewController: self, toViewController: leftViewController)

交互控制器與動畫控制器相聯

PresentAnimationController中添加以下屬性:

let interactionController: PresentInteractionController?

並添加init方法:

init(interactionController: PresentInteractionController?) {
    self.interactionController = interactionController
}

3. 實現過渡代理協議方法

動畫控制器forPresented方法中修改PresentAnimationController對象的創建。

func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        //展現VC是presented,源VC是presenting
        guard let _ = presented as? LeftViewController, let fromVC = presenting as? ViewController else {
            return nil
        }
        return PresentAnimationController(interactionController: fromVC.presentInteractionController)
    }

添加以下方法:

func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        guard let animator = animator as? PresentAnimationController,
            let interactionController = animator.interactionController,
            interactionController.interactionInProgress else {
                return nil
        }
        return interactionController
    }

這首先檢查所涉及的動畫控制器是否爲PresentAnimationController。如果是這樣,它將獲取關聯的交互控制器的引用,並驗證用戶交互正在進行中。如果不滿足這些條件中的任何一個,它將返回nil以便轉換將繼續進行而不會發生交互。否則,它將交互控制器交還給UIKit,以便它可以管理過渡。

dismiss狀態的交互式控制器的方法也是相近的,最終實現效果如下:

源碼下載

以上便是自定義過渡動畫的全部內容,讀者學習完後可以學一些進階動畫,參考一些優秀App的轉場動畫,進而創建自己過渡動畫,從而獲得良好的用戶體驗。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章