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

來自于PeteC/InteractiveViewControllerTransitions的定制過渡效果,這是個(gè)人非常喜歡的一種動(dòng)畫方式,習(xí)慣于成為元素重用,本章將要用Swift重寫這個(gè)項(xiàng)目,掌握針對(duì)于頁面的過渡動(dòng)畫,讓自己的APP更具個(gè)性化。

源碼見Github
demo為swift4版本,與文章代碼略有不同

頁面定制化的過渡方式同樣依賴于UIViewControllerAnimatedTransitioning, 如果還不是很了解,可以復(fù)習(xí)一下自定義navigation controller過渡動(dòng)畫


設(shè)計(jì)思路

  • 整個(gè)過渡動(dòng)畫的核心在于圖片的移動(dòng),看上去是將tableViewCell上的圖片移動(dòng)到下一頁控制器的view上,實(shí)際上我們會(huì)拷貝一份imageView從cell的位置移動(dòng)到視圖中心
  • 動(dòng)畫的其他部分無非與設(shè)置各個(gè)view的透明度

步驟

1.初始化項(xiàng)目

  • 兩層控制器
  • 相應(yīng)的視圖和模型
  • push和pop動(dòng)畫接口
  • 資源文件,詳見源碼
  • 控制器的初始化




    初始化的過程比較重復(fù),copy相應(yīng)的代碼就好

Swift Tip:
有一段初始化數(shù)據(jù)的代碼比較有意思,可以感受一下
這里用到了lazy關(guān)鍵字進(jìn)行懶加載,避免了oc里的判斷。在賦值的右邊,用到了一個(gè)匿名函數(shù),并且緊接括號(hào)執(zhí)行返回?cái)?shù)組,比較類似于JavaScript的匿名立即執(zhí)行函數(shù)

    lazy var things: [TTThing] = {
        let arr = []  // 通過字面量設(shè)置數(shù)組
        return arr
    }()

2.設(shè)置過渡動(dòng)畫

上一章已經(jīng)詳細(xì)說明過渡動(dòng)畫的設(shè)置方式,這里就直接進(jìn)入核心部分----動(dòng)畫效果的處理

1.過場(chǎng)時(shí)間設(shè)置

依舊以push過場(chǎng)為例,在新建的TTCustomPushAnimation.swift文件內(nèi),首先實(shí)現(xiàn)

func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
    return 1
}

設(shè)置過場(chǎng)時(shí)間為1s,1秒的速度方便調(diào)試

2.view獲取

然后來到動(dòng)畫大劇場(chǎng)

func animateTransition(transitionContext: UIViewControllerContextTransitioning)

animateTransition內(nèi)中設(shè)置
取得動(dòng)畫所需view

let containerView = transitionContext.containerView()
let fromVc = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as! TTCustomFromController
let toVc = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as! TTCustomToController
let cell = fromVc.collection.cellForItemAtIndexPath((fromVc.collection.indexPathsForSelectedItems()?.first)!) as! TTThingCell
let snapImageView = cell.imgView.snapshotViewAfterScreenUpdates(false)

除了過場(chǎng)必要的containerView、fromVc、toVc之外,我們還要通過collection取得點(diǎn)擊的cell,并將這個(gè)cell上的ImageView拷貝成snapImageView備用

Swift Tip:
這里在取得fromVc、toVc、cell時(shí)都用到了as!進(jìn)行強(qiáng)制類型轉(zhuǎn)換,加上感嘆號(hào)避免在之后調(diào)用中的可選解析
,但是在邏輯上就需要保證相應(yīng)類型的正確,后面的代碼會(huì)做相應(yīng)類型保護(hù)

3.過場(chǎng)參數(shù)設(shè)置

除了必要的動(dòng)畫時(shí)間,還需要獲得snapImageView在動(dòng)畫中的起始frame和終止frame(frame同時(shí)影響位置和大小)

let duration = self.transitionDuration(transitionContext)
let startFrame = cell.imgView.superview!.convertRect(cell.imgView.frame, toView: containerView)
let finalFrame = toVc.view.convertRect(toVc.imgView.frame, toView: containerView)

記得動(dòng)畫舞臺(tái)containerView么,snapImageView會(huì)在整個(gè)過場(chǎng)中置于其中進(jìn)行動(dòng)畫操作,所以我們使用convertRect將Cell上imageView的frame轉(zhuǎn)換到containerView作為起始frame,將toVc上imageView轉(zhuǎn)換到containerView作為終止frame

4.動(dòng)畫初始化

獲得所有動(dòng)畫所需的屬性后,接下來就是動(dòng)畫的準(zhǔn)備活動(dòng)了
1.將toVc的視圖和snapImageView添加到containerView上
2.將snapImageView的frame設(shè)置到起始frame,以覆蓋Cell上的imageView,并將Cell的imageView隱藏
3.將toVc的視圖透明度設(shè)置為0,并隱藏toVc上的imageView

containerView?.addSubview(toVc.view)
containerView?.addSubview(snapImageView)

snapImageView.frame = startFrame
cell.imgView.hidden = true
toVc.view.alpha = 0
toVc.imgView.hidden = true
5.動(dòng)畫設(shè)置

在整個(gè)動(dòng)畫中只有2個(gè)流程
1.讓toVc的視圖逐漸顯示出來
2.將snapImageView移動(dòng)到終止frame

UIView.animateWithDuration(duration, animations: { () -> Void in
    toVc.view.alpha = 1
    snapImageView.frame = finalFrame
    }) { (finished) -> Void in
        toVc.imgView.hidden = false
        cell.imgView.hidden = false
        snapImageView.removeFromSuperview()
        transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
}

動(dòng)畫結(jié)束之后,記得收?qǐng)雠?br> 1.移除snapImageView,并將toVc上的imageView顯示出來
2.將cell上的imageView恢復(fù)顯示
3.清除過場(chǎng)

3.設(shè)置push控制器

老規(guī)矩,上述操作寫好了劇本,得讓演員上臺(tái)表演了。由于這里是定制的過場(chǎng)動(dòng)畫,并不能重寫導(dǎo)航去影響所有的過場(chǎng),所以需要指定的演員TTCustomFirstController
在TTCustomFirstController中,添加UINavigationControllerDelegate,顯示控制器時(shí)添加代碼,不顯示時(shí)移除

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    self.navigationController?.delegate = self
}

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    if let _ = self.navigationController?.delegate {
        self.navigationController?.delegate = nil
    }
}

然后聲明此控制器的過場(chǎng)方式

func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    if fromVC == self && toVC is TTCustomSecondController {
        return TTCustomPushAnimation()
    }
    return nil
}

在導(dǎo)航的代理方法中,我們返回了自定義的過場(chǎng)動(dòng)畫接口,并對(duì)fromVc和toVc都做了類型判斷,還記得我們?cè)谏厦嬗玫降?code>as!強(qiáng)制類型轉(zhuǎn)換么,這里的判斷能保證類型的正確使用
重啟程序,push的過場(chǎng)已經(jīng)和預(yù)期的是一樣的了 ??

4.設(shè)置pop動(dòng)畫

push動(dòng)畫已經(jīng)設(shè)置完畢,pop動(dòng)畫依舊是push的逆向過程

1.初始化pop過場(chǎng)動(dòng)畫

新建TTCustomPopAnimation,實(shí)現(xiàn)UIViewControllerAnimatedTransitioning的兩個(gè)方法,這里直接貼出pop動(dòng)畫的設(shè)置代碼
依舊需要注意的是fromVc和toVc對(duì)應(yīng)的控制器

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    let containerView = transitionContext.containerView()
    let fromVc = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as! TTCustomSecondController
    let toVc = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as! TTCustomFirstController
    let selectedCell = toVc.collection.cellForItemAtIndexPath(toVc.selectedIndex!) as! TTThingCell
    let snapImgView = fromVc.imgView.snapshotViewAfterScreenUpdates(false)

    let duration = self.transitionDuration(transitionContext)
    let startFrame = fromVc.view.convertRect(fromVc.imgView.frame, toView: containerView)
    let finalFrame = selectedCell.imgView.convertRect(selectedCell.imgView.frame, toView: containerView)

    snapImgView.frame = startFrame
    fromVc.imgView.hidden = true
    toVc.view.alpha = 0

    containerView?.insertSubview(toVc.view, belowSubview: fromVc.view)
    containerView?.addSubview(snapImgView)

    UIView.animateWithDuration(duration, animations: { () -> Void in
        toVc.view.alpha = 1
        fromVc.view.alpha = 0
        snapImgView.frame = finalFrame
        }) { (finished) -> Void in
            fromVc.imgView.hidden = false
            selectedCell.imgView.hidden = false
            snapImgView.removeFromSuperview()
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
    }
}

pop動(dòng)畫的設(shè)置還是有幾個(gè)小坑的:
1.需要取得控制器跳轉(zhuǎn)前點(diǎn)擊的那個(gè)cell,這里采用了簡(jiǎn)化的方法,在cell點(diǎn)擊時(shí),將index記錄在selectedIndex,方便pop的時(shí)候直接取用
2.注意各個(gè)視圖透明度和hidden的控制

2.設(shè)置pop控制器

重復(fù)設(shè)置push控制器的流程,在TTCustomSecondController中,添加UINavigationControllerDelegate并實(shí)現(xiàn)導(dǎo)航代理方法

func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    if fromVC.isEqual(self) && toVC is TTCustomFirstController {
        return TTCustomPopAnimation()
    }
    return nil
}

再次啟動(dòng)項(xiàng)目,pop的動(dòng)畫也能夠正常工作了

5.手勢(shì)返回

跟之前一樣,自定義過場(chǎng)之后,右劃手勢(shì)返回會(huì)失效,需要重新設(shè)置,這里就不重復(fù)這部分內(nèi)容了,但是新的手勢(shì)需要加到TTCustomSecondController控制器內(nèi),自定義手勢(shì)


如果你也喜愛游戲,歡迎支持我的APP Up 游戲?qū)]?/a>

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

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