Swift開發小技巧--自定義轉場動畫


自定義轉場動畫

  • 個人理解為重寫了被彈出控制器的modal樣式,根據自己的樣式來顯示modal出來的控制器
例:presentViewController(aVC, animated: true, completion: nil)

1.為了實現如圖,modal出來的aVC控制器有下圖這樣的效果

  • 首先,需要在modal出來之前,設置aVC的自定義轉場動畫的樣式為自定義(UIModalPresentationStyle.Custom)

  • 其次,設置自定義轉場動畫的代理,協議 UIViewControllerTransitioningDelegate(代理可以是當前控制器,項目中為了減少當前控制器的代碼量,單獨抽取了一個類)

// 2.創建菜單
let sb = UIStoryboard(name: "PopoverMenuViewController", bundle: nil)
let menuVC = (sb.instantiateInitialViewController())!

// 自定義轉場動畫
// 2.1設置自定義轉場動畫的樣式
menuVC.modalPresentationStyle = UIModalPresentationStyle.Custom
// 2.2設置轉場動畫的代理
menuVC.transitioningDelegate = presentationMgr

// 3.彈出菜單,modal默認的彈出樣式是從上往下,而且還不能控制彈出控制器的大小
presentViewController(menuVC, animated: true, completion: nil)

2.UIViewControllerTransitioningDelegate協議中的三個方法

  • 方法一(返回一個負責轉場動畫的對象,轉場動畫完成后顯示的尺寸和位置,可以在這個負責轉場動畫對象中設置,所以說要自定義這個類,在自定義的負責轉場動畫的類中設置自己需要的位置和尺寸) :
    func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController?
    {
        // 自定義負責轉場的對象ChaosPresentationController
        let pc = ChaosPresentationController(presentedViewController: presented, presentingViewController: presenting)
        // 位置由外界決定
        pc.presentViewFrame = presentViewFrame
        return pc
    }

2.1自定義自定義負責轉場的對象ChaosPresentationController : UIPresentationController,也就是自定義轉場.作用:

1.如果不自定義轉場,modal出來的控制器會移除原有的控制器

2.如果不自定義轉場,modal出來的控制器不會移除原有的控制器

3.如果不自定義轉場,modal出來的控制器的尺寸和屏幕一樣

4.如果自定義轉場,modal出來的控制器的尺寸可以在containerViewWillLayoutSubviews方法中控制

5.containerView 非常重要,容器試圖,所有modal出來的視圖都添加到了containerView上

6.presentedView() 非常重要,通過該方法能夠拿到彈出的視圖

  • 決定彈出視圖位置的實現思路有兩種
    // 第一種 : 重寫containerViewWillLayoutSubviews方法,用於布局轉場動畫彈出的控件
    override func containerViewWillLayoutSubviews() {
        super.containerViewWillLayoutSubviews()

//        containerView 非常重要,容器試圖
//        presentedView() 非常重要,通過該方法能夠拿到彈出的視圖
        presentedView()?.frame = presentViewFrame // 位置尺寸有外界來決定

        // 將HUD添加到containerView上
        containerView?.insertSubview(HUD, atIndex: 0)
    }
    // 第二種 : 重寫frameOfPresentedViewInContainerView方法,返回容器中view的frame
    override func frameOfPresentedViewInContainerView() -> CGRect {
         return CGRect(x: 85, y: 50, width: 200, height: 300)
    }
  • 方法二: 控制器出現的時候會調用該方法 該方法用於返回一個負責轉場動畫如何出現的對象,需要遵守UIViewControllerAnimatedTransitioning協議
    func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        // 返回當前控制器,讓當前控制器來負責轉場怎么出現,並且當前控制器要遵循UIViewControllerAnimatedTransitioning協議
        ChaosLog("出現")
        // 發送通知,告訴外界狀態發生改變
        NSNotificationCenter.defaultCenter().postNotificationName(ChaosPresentationManagerDidPresent, object: self)
        isPresent = true // 改變標記的值
        return self
    }
  • 方法三: 控制器隱藏的時候會調用該方法 該方法用於返回一個負責轉場動畫如何消失的對象,需要遵守UIViewControllerAnimatedTransitioning協議
    func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        // 返回當前控制器,讓當前控制器來負責轉場怎么消失,並且當前控制器要遵循UIViewControllerAnimatedTransitioning協議
        ChaosLog("消失")
        // 發送通知,告訴外界狀態發生改變
        NSNotificationCenter.defaultCenter().postNotificationName(ChaosPresentationManagerDidPresent, object: self)
        isPresent = false // 改變標記的值
        return self
    }

3.UIViewControllerAnimatedTransitioning協議中的兩個重要方法

  • 方法一 : 用來告訴系統展示動畫和消失動畫所用的時長,用來統一轉場動畫顯示和消失所用的時間
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {

        return 0.3
    }
  • 重點方法二 : 專門用於管理modal以什么樣的動畫展現和消失的,無論是展現還是消失都會調用該方法

<1> 專門用於管理modal以什么樣的動畫顯示消失的,無論展示和消失都會調用這個方法 <2> 只要實現了這個方法,系統就不會默認添加動畫了(modal系統默認動畫是從上往下鑽) ❤️> 所有的動畫效果都需要我們自己實現,包括需要展示的視圖也需要我們自己添加到容器的視圖上(containerView) <4> transitionContext : 所有動畫需要的屬性等都保存在上下文中,換而言之就是可以通過transitionContext獲取我們想要的東西

    func animateTransition(transitionContext: UIViewControllerContextTransitioning)
    {
        if isPresent { // 顯示

            willPresentViewController(transitionContext)
        } else { // 消失

            willDismissViewController(transitionContext)
        }
    }

    // 顯示視圖的方法
    private func willPresentViewController(transitionContext: UIViewControllerContextTransitioning) {

        //        // 將要modal出來的VC
        //        let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
        //        // 控制toVC顯示出來的VC
        //        let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
        //        ChaosLog(toVC)
        //        ChaosLog(fromVC)

        // 對應toVC的View
        guard let toView = transitionContext.viewForKey(UITransitionContextToViewKey) else {
            return
        }
        // 對應fromVC的View
        //        let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)

        // 獲取containerView
        transitionContext.containerView()?.addSubview(toView)
        // 動畫效果
        toView.transform = CGAffineTransformMakeScale(1.0, 0.0)
        // layer的錨點默認在中間,動畫也是從中間開始,將錨點設置為頂部
        toView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.0)
        UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
            toView.transform = CGAffineTransformIdentity
            }) { (_) in
                // 注意: 自定義轉場動畫,在執行完動畫之后一定要告訴系統動畫執行完畢了
                // 剛開始自己忘了,然后PopoverMenu上的TableView不見了
                transitionContext.completeTransition(true)
        }
    }

    // dismiss視圖的方法
    private func willDismissViewController(transitionContext: UIViewControllerContextTransitioning) {

        guard let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey) else {
            return
        }
        UIView.animateWithDuration(transitionDuration(transitionContext), animations: { () -> Void in

            // menu沒有動畫消失,而是立即消失的原因是CGFloat不准確,導致動畫無法執行.
            // 解決方案: 給CGFloat設置一個很小的值即可
            fromView.transform = CGAffineTransformMakeScale(1.0, 0.00001)
            }, completion: { (_) -> Void in

                transitionContext.completeTransition(true)
        })
    }

自定義轉場動畫細節問題

1. 導航欄的titlesView中箭頭方向改變的問題 : 剛開始的做法是根據選中狀態,修改箭頭的位置.但是項目中的實際效果為,點擊titlesView出現菜單控制器,點擊除了titlesView的任何地方菜單控制器消失,如圖

  • 解決方案 : 在代理方法中有發出通知,外界通過監聽通知來改變titlesView中箭頭的方向
    • 通知的字符串常量
    • 分別在菜單控制器顯示和消失的方法中發出通知
    • 外界通過監聽通知來改變titlesView中箭頭方向

2. 自定義轉場動畫結束后,一定要告訴系統,動畫結束

// 注意: 自定義轉場動畫,在執行完動畫之后一定要告訴系統動畫執行完畢了
// 剛開始自己忘了,然后PopoverMenu上的TableView不見了
transitionContext.completeTransition(true)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM