[iOS]自定義ViewController的轉(zhuǎn)場動畫 (Swift)

Presentation ViewController


基礎(chǔ)知識

在沒有UINavigationController的時候,我們通常用present modally(彈出模態(tài)控制器)的方式切換視圖。默認(rèn)情況下,目標(biāo)視圖從屏幕的下方彈出。具體方法是:

通過presentViewController(_: animated:completion:)來彈出視圖,
通過viewControllermodalTransitionStyle的屬性設(shè)置彈出ViewController時的動畫:

viewController.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve
presentViewController(secondViewController, animated: true, completion: nil)

系統(tǒng)自帶的四種動畫有:

enum UIModalTransitionStyle: Int {
    case CoverVertical   // 底部滑入,默認(rèn)
    case FlipHorizontal  // 水平翻轉(zhuǎn)
    case CrossDissolve   // 隱出隱現(xiàn)
    case PartialCurl     // 翻頁
}

自定義轉(zhuǎn)場動畫

在轉(zhuǎn)場的過程中系統(tǒng)會提供一個視圖容器containerView,當(dāng)前的視圖(fromView)會自動加入到這個容器中,我們所要做的就是將目標(biāo)視圖(toView)加入到這個容器中,然后為目標(biāo)視圖的展現(xiàn)增加動畫。

需要注意的是:如果是從 A 視圖控制器 present 到 B,則A是fromView,B是toView。從 B 視圖控制器dismiss到 A 時,B 變成了fromView,A是toView。

創(chuàng)建動畫管理器類,該類需繼承NSObject并遵循UIViewControllerAnimatedTransitioning協(xié)議:

import UIKit

class FadeAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    
    let duration = 1.0
   
    // 指定轉(zhuǎn)場動畫持續(xù)的時間
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
        return duration
    }
    
    // 實現(xiàn)轉(zhuǎn)場動畫的具體內(nèi)容
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        
        // 得到容器視圖
        let containerView = transitionContext.containerView()
        // 目標(biāo)視圖
        let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!

        containerView.addSubview(toView)
        
        // 為目標(biāo)視圖的展現(xiàn)添加動畫
        toView.alpha = 0.0
        UIView.animateWithDuration(duration,
            animations: {
                toView.alpha = 1.0
            }, completion: { _ in
                transitionContext.completeTransition(true)
        })
    }
}

然后在ViewController(第一個VC)中加入如下代碼:

// 聲明一個動畫實例
let transition = FadeAnimator()

// 遵循 UIViewControllerTransitioningDelegate 協(xié)議,并實現(xiàn)其中的兩個方法:

extension ViewController: UIViewControllerTransitioningDelegate {
    // 提供彈出時的動畫
    func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        return transition
    }
    // 提供消失時的動畫
    func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        return transition
    }
}
@IBAction func register(sender: AnyObject) {
    var viewController = storyboard!.instantiateViewControllerWithIdentifier("SecondViewController") as SecondViewController
    viewController.transitioningDelegate = self
        
    presentViewController(viewController, animated: true, completion: nil)
}

最終效果如下:

效果展示

Segue


通過Segue轉(zhuǎn)場,即通過在Storyboard中拖線的方式進(jìn)行轉(zhuǎn)場。自定義轉(zhuǎn)場動畫的關(guān)鍵就是:

  • 創(chuàng)建一個UIStoryboardSegue的子類,并重載其中perform方法。
  • 在perform方法中實現(xiàn)自定義動畫的邏輯。
  • 將這個UIStoryboardSegue類與拖的segue線進(jìn)行綁定。

下面我們一步步來實現(xiàn):

創(chuàng)建兩個UIViewController類——FirstViewController、SecondViewController,分別對應(yīng)Storyboard中的兩個ViewController場景,從第一個VC向第二個VC拖一個Segue,并選擇custom:

Storyboard.png

創(chuàng)建一個UIStoryboardSegue子類,并與上一部的Segue進(jìn)行綁定,并指定它的Identifier:

Segue.png

打開FirstCustomSegue.swift,在perform方法中實現(xiàn)具體動畫:

import UIKit

class FirstCustomSegue: UIStoryboardSegue {
   
    // 重載perform方法,在這里我們添加想要的自定義邏輯
    override func perform() {
        
        // 得到源控制器和目標(biāo)控制器的視圖
        var sourceView = self.sourceViewController.view as UIView!
        var destinationView = self.destinationViewController.view as UIView!
        
        // 得到屏幕的寬和高
        let screenWidth = UIScreen.mainScreen().bounds.size.width
        let screenHeight = UIScreen.mainScreen().bounds.size.height
        
        // 把destinationView放在sourceView的下面
        destinationView.frame = CGRectMake(0.0, screenHeight, screenWidth, screenHeight)
        
        let window = UIApplication.sharedApplication().keyWindow
        // 把目標(biāo)視圖添加到當(dāng)前視圖中
        window?.insertSubview(destinationView, aboveSubview: sourceView)
        
        UIView.animateWithDuration(0.4, animations: { () -> Void in
            
            sourceView.frame = CGRectOffset(sourceView.frame, 0.0, -screenHeight)
            destinationView.frame = CGRectOffset(destinationView.frame, 0.0, -screenHeight)
            
            }, completion: { _ in
                // 展示新的視圖控制器
                self.sourceViewController.presentViewController(self.destinationViewController as UIViewController, animated: false, completion: nil)
        })
    }
}

在FirstViewController中執(zhí)行這個自定義轉(zhuǎn)場,這里我們采用滑動手勢:

    override func viewDidLoad() {
        super.viewDidLoad()

        var swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "showSecondViewController")
        swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Up
        
        self.view.addGestureRecognizer(swipeGestureRecognizer)
    }

    func showSecondViewController() {
        self.performSegueWithIdentifier("FirstCustomSegue", sender: self)
    }

解除轉(zhuǎn)場

與轉(zhuǎn)場對應(yīng)的是解除轉(zhuǎn)場(unwind segue),和正常轉(zhuǎn)場一樣,也需要創(chuàng)建一個UIStoryboardSegue的子類,并重載其中perform方法。
不同的是,解除轉(zhuǎn)場需要在第一個ViewController中創(chuàng)建一個帶UIStoryboardSegue類參數(shù)的IBAction方法,這個方法的內(nèi)容可以為空,但必須存在。

需要注意的是,F(xiàn)irstViewController沒有UINavigationController的時候解除轉(zhuǎn)場的動畫才會出現(xiàn),但是正常轉(zhuǎn)場效果是都有的,不知道這是不是個漏洞,知道的老師可以告訴下,謝謝。

下面我們來實現(xiàn)這個自定義解除轉(zhuǎn)場:

打開FirstViewController.swift,添加代碼:

 @IBAction func backFromSegueAction(sender: UIStoryboardSegue) {
        
 }

打開Storyboard,在SecondViewController場景中進(jìn)行如下操作:

1.png

然后在Scene中會出現(xiàn)Unwind segue,選中后在屬性中設(shè)置Identifier:backFromSegue

222.png

創(chuàng)建UIStoryboardSegue子類-FirstCustomSegueUnwind,重寫其中的perform方法:

import UIKit

class FirstCustomSegueUnwind: UIStoryboardSegue {

    override func perform() {
        
        var secondVCView = self.sourceViewController.view as UIView!
        var firstVCView = self.destinationViewController.view as UIView!
        
        let screenHeight = UIScreen.mainScreen().bounds.size.height
        
        let window = UIApplication.sharedApplication().keyWindow
        window?.insertSubview(firstVCView, aboveSubview: secondVCView)
        
        UIView.animateWithDuration(0.4, animations: { () -> Void in
            firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, screenHeight)
            secondVCView.frame = CGRectOffset(secondVCView.frame, 0.0, screenHeight)
            
            }) { (Finished) -> Void in
                
                self.sourceViewController.dismissViewControllerAnimated(false, completion: nil)
        }
    }
}

接下來在FirstViewController中來調(diào)用這個子類,打開FirstViewController.swift,重寫以下方法:

    override func segueForUnwindingToViewController(toViewController: UIViewController, fromViewController: UIViewController, identifier: String?) -> UIStoryboardSegue {

        if let id = identifier {
            if id == "backFromSegue" {
                let unwindSegue = FirstCustomSegueUnwind(identifier: id, source: fromViewController, destination: toViewController, performHandler: { () -> Void in
                    
                })
                return unwindSegue
            }
        }
        
        return super.segueForUnwindingToViewController(toViewController, fromViewController: fromViewController, identifier: identifier)
    }

最后,在SecondViewController中執(zhí)行這個解除轉(zhuǎn)場,還是采用滑動手勢:

import UIKit

class SecondViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        var swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "showFirstViewController")
        swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Down
        
        self.view.addGestureRecognizer(swipeGestureRecognizer)
    }

    func showFirstViewController() {
        self.performSegueWithIdentifier("backFromSegue", sender: self)
    }
}

到這里,整個轉(zhuǎn)場就全部寫完了,最后看下效果:

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

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

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