[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中拖線的方式進行轉(zhuǎn)場动遭。自定義轉(zhuǎn)場動畫的關(guān)鍵就是:

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

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

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

Storyboard.png

創(chuàng)建一個UIStoryboardSegue子類膳殷,并與上一部的Segue進行綁定膨疏,并指定它的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場景中進行如下操作:

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)系作者
  • 序言:七十年代末惫霸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子葱弟,更是在濱河造成了極大的恐慌壹店,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翘悉,死亡現(xiàn)場離奇詭異茫打,居然都是意外死亡居触,警方通過查閱死者的電腦和手機妖混,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轮洋,“玉大人制市,你說我怎么就攤上這事”子瑁” “怎么了祥楣?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我误褪,道長责鳍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任兽间,我火速辦了婚禮历葛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嘀略。我一直安慰自己恤溶,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布帜羊。 她就那樣靜靜地躺著咒程,像睡著了一般。 火紅的嫁衣襯著肌膚如雪讼育。 梳的紋絲不亂的頭發(fā)上帐姻,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天,我揣著相機與錄音奶段,去河邊找鬼卖宠。 笑死,一個胖子當(dāng)著我的面吹牛忧饭,可吹牛的內(nèi)容都是我干的扛伍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼词裤,長吁一口氣:“原來是場噩夢啊……” “哼刺洒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起吼砂,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤逆航,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后渔肩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體因俐,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年周偎,在試婚紗的時候發(fā)現(xiàn)自己被綠了抹剩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡蓉坎,死狀恐怖澳眷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蛉艾,我是刑警寧澤钳踊,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布衷敌,位于F島的核電站,受9級特大地震影響拓瞪,放射性物質(zhì)發(fā)生泄漏缴罗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一祭埂、第九天 我趴在偏房一處隱蔽的房頂上張望瞒爬。 院中可真熱鬧,春花似錦沟堡、人聲如沸侧但。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽禀横。三九已至,卻和暖如春粥血,著一層夾襖步出監(jiān)牢的瞬間柏锄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工复亏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留趾娃,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓缔御,卻偏偏與公主長得像抬闷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子耕突,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內(nèi)容