自定義過渡動(dòng)畫

歷時(shí)5天從各種英文教程中學(xué)習(xí)到的過渡動(dòng)畫,是一個(gè)很難忘的探索經(jīng)歷

比較好的參考文章自定義UIViewController過渡入門動(dòng)畫入門

轉(zhuǎn)場(chǎng)方式

首先讓我們來了解iOS轉(zhuǎn)場(chǎng)的方式:

  • UINavigationController push/pop UIViewController導(dǎo)航欄的轉(zhuǎn)場(chǎng)
  • UITabBarController 切換Tab的轉(zhuǎn)場(chǎng)
  • present/dismiss 模態(tài)的方式轉(zhuǎn)場(chǎng)

這是iOS提供的3種基本轉(zhuǎn)場(chǎng)方式,默認(rèn)的轉(zhuǎn)場(chǎng)方式轉(zhuǎn)場(chǎng)風(fēng)格有限。例如模態(tài)轉(zhuǎn)場(chǎng)中,盡管有modalPresentationStylemodalTransitionStyle關(guān)于展現(xiàn)風(fēng)格和過渡風(fēng)格的設(shè)置,但是轉(zhuǎn)場(chǎng)仍是死板從底部彈出。這并不能滿足我們?cè)谲浖_發(fā)的需求,側(cè)邊欄、頂部欄的動(dòng)畫效果都無法很好地實(shí)現(xiàn)。在iOS 7.0之后Apple提供一套完整的自定義過渡動(dòng)畫的API,為各種轉(zhuǎn)場(chǎng)動(dòng)畫的實(shí)現(xiàn)帶了無限可能。

這里主要介紹模態(tài)轉(zhuǎn)場(chǎng)的自定義動(dòng)畫。
頂部欄的動(dòng)畫效果:

15.gif

SlideMune 的源碼

Modal 轉(zhuǎn)場(chǎng)

模態(tài)轉(zhuǎn)場(chǎng)分為非交互式轉(zhuǎn)場(chǎng)交互式轉(zhuǎn)場(chǎng)。
非交互式轉(zhuǎn)場(chǎng)也就是普通轉(zhuǎn)場(chǎng),轉(zhuǎn)場(chǎng)的動(dòng)畫無法交互,不能在動(dòng)畫的過程中終止轉(zhuǎn)場(chǎng)。
交互式轉(zhuǎn)場(chǎng)能通過手指觸摸屏幕通過滑動(dòng)體驗(yàn)過渡動(dòng)畫的進(jìn)行,并能終止動(dòng)畫過程。

在這里插入圖片描述

過渡動(dòng)畫API

我們定義:如果視圖控制器Apresent之后展示視圖控制器B。在后文中,源視圖控制器fromVC目標(biāo)視圖控制器toVC。

狀態(tài) 視圖控制器A 視圖控制器B
Present 源視圖控制器 目標(biāo)視圖控制器
Dissmiss 目標(biāo)視圖控制器 源視圖控制器
在這里插入圖片描述

transitioningDelegate 過渡代理

每個(gè)視圖控制器UIViewController都有一個(gè)transitioningDelegate屬性,該代理需遵循UIViewControllerTransitioningDelegate協(xié)議,提供相關(guān)動(dòng)畫控制器。

每當(dāng)您顯示或關(guān)閉視圖控制器時(shí),UIKit都會(huì)要求其過渡代理使用動(dòng)畫控制器。要將默認(rèn)動(dòng)畫替換為您自己的自定義動(dòng)畫,必須實(shí)現(xiàn)過渡代理,并使其返回適當(dāng)?shù)膭?dòng)畫控制器。

AnimationController 動(dòng)畫控制器

過渡代理在present/dismiss時(shí)返回相應(yīng)的動(dòng)畫控制器。動(dòng)畫控制器是過渡動(dòng)畫的核心。它完成了動(dòng)畫過渡的“繁重工作”。

TransitioningContext 過渡語境

過渡語境在過渡過程中實(shí)現(xiàn)并起著至關(guān)重要的作用:它封裝了有關(guān)過渡中涉及的視圖和視圖控制器的信息。過渡語境輔助動(dòng)畫控制器實(shí)現(xiàn)動(dòng)畫。
從圖中可以看出,自己并沒有實(shí)現(xiàn)此協(xié)議。UIKit會(huì)為您創(chuàng)建和配置過渡上下文,并在每次發(fā)生過渡時(shí)將其傳遞給動(dòng)畫控制器。

非交互式過渡動(dòng)畫的過程

以present過渡動(dòng)畫為例:

  1. 通過代碼或StoryBoard segue觸發(fā)模態(tài)視圖present過程。
  2. UIKit向toVC(目標(biāo)視圖控制器)請(qǐng)求其過渡代理。如果沒有,UIKIt將使用標(biāo)準(zhǔn)的內(nèi)置過渡。
  3. 然后,UIKit通過來向過渡代理請(qǐng)求動(dòng)畫控制器animationController(forPresented:presenting:source:)。如果返回nil,則過渡將使用默認(rèn)動(dòng)畫。
  4. UIKit構(gòu)造過渡語境。UIKit通過調(diào)用向動(dòng)畫控制器詢問動(dòng)畫的持續(xù)時(shí)間transitionDuration(using:)。UIKit animateTransition(using:)在動(dòng)畫控制器上調(diào)用以執(zhí)行過渡的動(dòng)畫。
  5. 最后,動(dòng)畫控制器調(diào)用completeTransition(_:)過渡上下文以指示動(dòng)畫已完成。

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

非交互式過渡動(dòng)畫需要提供的條件

  1. 設(shè)置過渡動(dòng)畫代理。設(shè)置(目標(biāo)視圖控制器)的transitioningDelegate屬性,即設(shè)置過渡動(dòng)畫代理對(duì)象,該代理對(duì)象遵循UIViewControllerTransitioningDelegate協(xié)議,實(shí)現(xiàn)forPresentedforDismissed兩個(gè)方法,分別提供present和dismiss的視圖控制器實(shí)例。
  2. 創(chuàng)建動(dòng)畫控制器。創(chuàng)建present和dismiss的動(dòng)畫控制器,實(shí)現(xiàn)動(dòng)畫持續(xù)時(shí)間和構(gòu)造動(dòng)畫方法。

這里我們完成一個(gè)簡(jiǎn)單的從左到右的過渡動(dòng)畫。

1. 構(gòu)建present/dimiss場(chǎng)景

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

2. 創(chuàng)建Animation Controller

Animation Controller是自定義過渡動(dòng)畫的核心對(duì)象。
AnimationControlle繼承至NSObject,遵循UIViewControllerAnimatedTransitioning協(xié)議,實(shí)現(xiàn)兩個(gè)required方法。

class AnimationController: NSObject, UIViewControllerAnimatedTransitioning {

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        //要求提供動(dòng)畫師對(duì)象的動(dòng)畫持續(xù)時(shí)間屬性
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        //過渡動(dòng)畫的實(shí)現(xiàn)效果,也是自定義過渡動(dòng)畫的核心方法,需要構(gòu)建動(dòng)畫的實(shí)現(xiàn)。
    }
}

這里貼出Present狀態(tài)下的AnimationController的代碼,Dismiss狀態(tài)與其類似(動(dòng)畫過程執(zhí)行反過程)。

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

在第一個(gè)transitionDuration(using:)方法中設(shè)置動(dòng)畫的持續(xù)時(shí)間。

在第二個(gè)transitionDuration(using:)方法中構(gòu)建動(dòng)畫。

  1. 獲取過渡動(dòng)畫所需的視圖控制器及snapshot。從過渡語境中transitionContext.viewController我們可以獲取源視圖控制器fromVC目標(biāo)視圖控制器toVC,過渡語境封裝了設(shè)計(jì)的視圖控制器的信息,極大地幫助我們處理視圖控制器的動(dòng)畫轉(zhuǎn)化。還可以獲取fromVC和toVC的snapshot(屏幕快照),來構(gòu)造更加復(fù)雜和優(yōu)秀的動(dòng)畫。
  2. 管理過渡語境的容器視圖 --- containerView和視圖動(dòng)畫的位置初始化。UIKit將整個(gè)過渡封裝在容器視圖中,以簡(jiǎn)化視圖層次結(jié)構(gòu)和動(dòng)畫的管理,容器視圖負(fù)責(zé)管理fromVC.viewtoVC.view。由UIKit創(chuàng)建的容器視圖僅包含fromVC視圖。您必須添加任何其他將參與過渡的視圖。

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

  1. 設(shè)置動(dòng)畫效果。動(dòng)畫有兩種實(shí)現(xiàn)方法,一種是基礎(chǔ)動(dòng)畫animate,另一種是關(guān)鍵幀動(dòng)畫animateKeyframes。這里只是簡(jiǎn)單地將fromVC.view從屏幕的左邊界外移動(dòng)到屏幕中。<font color = red>注意:在動(dòng)畫完成后需要調(diào)用completeTransition(_:)通知UIKit動(dòng)畫已完成。這將確保最終狀態(tài)是一致的。</font>

3.設(shè)置過渡動(dòng)畫代理

設(shè)置(目標(biāo)視圖控制器)destinationVC的modalPresentationStyle為枚舉屬性custom,過渡動(dòng)畫的代理為self即(源視圖控制器)ViewController。

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

然后在(源視圖控制器)添加擴(kuò)展,遵循UIViewControllerTransitioningDelegate協(xié)議,實(shí)現(xiàn)forPresentedforDismissed方法。

extension ViewController: UIViewControllerTransitioningDelegate {
    
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        //目標(biāo)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()
    }
}

這樣就構(gòu)建好了一個(gè)簡(jiǎn)單、基礎(chǔ)的自定義轉(zhuǎn)場(chǎng)過渡動(dòng)畫。

交互式過渡動(dòng)畫

交互式動(dòng)畫會(huì)使使用者的動(dòng)畫體驗(yàn)更自然舒適而不顯得尖銳,給控制動(dòng)畫過程留下了余地。iOS原生APP設(shè)置中便有這樣的交互式動(dòng)畫的例子。

過渡動(dòng)畫的進(jìn)度跟隨手指的滑動(dòng)來啟動(dòng)/終止轉(zhuǎn)場(chǎng)動(dòng)畫,這樣可以帶來良好的用戶交互體驗(yàn)。

交互式控制器的工作方式

交互控制器通過加快,減慢甚至反轉(zhuǎn)過渡的過程來響應(yīng)觸摸事件或編程輸入。為了啟用交互式轉(zhuǎn)換,轉(zhuǎn)換代理必須提供一個(gè)交互控制器。您已經(jīng)制作了過渡動(dòng)畫。在過渡動(dòng)畫的基礎(chǔ)上,Apple將交互式動(dòng)畫封裝成交互控制器將響應(yīng)手勢(shì)來管理此動(dòng)畫,而不是讓其像視頻一樣播放。Apple提供了現(xiàn)成的UIPercentDrivenInteractiveTransition類,通過繼承該類來創(chuàng)建自己的交互式控制器。

1. 創(chuàng)建交互式控制器

我們?cè)谥斑^渡動(dòng)畫的基礎(chǔ)上構(gòu)建交互式過渡動(dòng)畫,我們首先需要?jiǎng)?chuàng)建交互式控制器。這里貼出顯示交互式控制器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屬性,表示交互式場(chǎng)景是否在發(fā)生。
  • shouldCompleteTrantisionBool屬性,表示是否應(yīng)該終止過渡動(dòng)畫。用于內(nèi)部管理過渡。
  • viewControllertoViewController,獲取源視圖控制器和目標(biāo)視圖控制器的引用,用于管理過渡某狀態(tài)present視圖控制器,達(dá)到交互式控制器與動(dòng)畫控制器相聯(lián)系的作用。
  • prepareGestureRecognizer(in:)為源視圖添加屏幕手勢(shì)的方法,這里的交互式動(dòng)畫為從左往右present出VC,所以為源視圖添加屏幕相應(yīng)在.left的手勢(shì)。
  • handleGsture(_:)為相應(yīng)手勢(shì)變化從而改變過渡動(dòng)畫狀態(tài)的方法。通過聲明局部變量以跟蹤滑動(dòng)進(jìn)度translation,根據(jù)translation在視圖中獲取并計(jì)算過渡進(jìn)度progress。手勢(shì)開始時(shí),您將設(shè)置interactionInProgresstrue并觸發(fā)prsent視圖控制器。手勢(shì)移動(dòng)時(shí),調(diào)用update(_:)更新過渡進(jìn)度。這是一種根據(jù)UIPercentDrivenInteractiveTransition您傳入的百分比移動(dòng)過渡的方法。如果取消手勢(shì),則更新interactionInProgress并取消過渡。一旦動(dòng)作已經(jīng)結(jié)束,您使用的過渡進(jìn)度來決定cancel()finish()。

2. 控制器聯(lián)系

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

viewController中添加以下屬性:

var presentInteractionController: PresentInteractionController?

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

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

交互控制器與動(dòng)畫控制器相聯(lián)

PresentAnimationController中添加以下屬性:

let interactionController: PresentInteractionController?

并添加init方法:

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

3. 實(shí)現(xiàn)過渡代理協(xié)議方法

動(dòng)畫控制器forPresented方法中修改PresentAnimationController對(duì)象的創(chuàng)建。

func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        //展現(xiàn)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
    }

這首先檢查所涉及的動(dòng)畫控制器是否為PresentAnimationController。如果是這樣,它將獲取關(guān)聯(lián)的交互控制器的引用,并驗(yàn)證用戶交互正在進(jìn)行中。如果不滿足這些條件中的任何一個(gè),它將返回nil以便轉(zhuǎn)換將繼續(xù)進(jìn)行而不會(huì)發(fā)生交互。否則,它將交互控制器交還給UIKit,以便它可以管理過渡。

dismiss狀態(tài)的交互式控制器的方法也是相近的,最終實(shí)現(xiàn)效果如下:

2020-02-03 14.50.13.gif

源碼下載

以上便是自定義過渡動(dòng)畫的全部?jī)?nèi)容,讀者學(xué)習(xí)完后可以學(xué)一些進(jìn)階動(dòng)畫,參考一些優(yōu)秀App的轉(zhuǎn)場(chǎng)動(dòng)畫,進(jìn)而創(chuàng)建自己過渡動(dòng)畫,從而獲得良好的用戶體驗(yàn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容