Swift版抽屜效果,自定義轉(zhuǎn)場(chǎng)動(dòng)畫管理器

效果展示

image

iOS7.0加入了自定義轉(zhuǎn)場(chǎng)動(dòng)畫,淘汰了之前左右兩大隱藏護(hù)法的抽屜效果,并且一些浮窗、彈層都可以用vc來顯示了,不再是用view蓋在window上

看了一些抽屜Demo發(fā)覺都是OC寫的,本篇使用Swift4.0編寫一個(gè)純正Swift版轉(zhuǎn)場(chǎng)動(dòng)畫管理器,其中用到元組,OC混編可能需要改為字典... 目前已經(jīng)支持oc混編


自定義轉(zhuǎn)場(chǎng)動(dòng)畫協(xié)議

1、UINavigationControllerDelegate

push和pop轉(zhuǎn)場(chǎng)動(dòng)畫協(xié)議,主要用到的方法有兩個(gè)

optional public func navigationController(_ 
    navigationController: UINavigationController, 
    animationControllerFor operation: UINavigationControllerOperation, 
    from fromVC: UIViewController, 
    to toVC: UIViewController)
    -> UIViewControllerAnimatedTransitioning?

參數(shù)navigationController當(dāng)前執(zhí)行動(dòng)畫的導(dǎo)航欄
operation用來判斷push還是pop來實(shí)現(xiàn)不同動(dòng)畫
fromVC和toVC,A push B,A是fromVC,B是toVC,B pop,B是fromVC,A是toVC,顧名思義沒什么好說的
最后返回需要執(zhí)行的動(dòng)畫

optional public func navigationController(_ 
    navigationController: UINavigationController, 
    interactionControllerFor animationController: UIViewControllerAnimatedTransitioning)
    -> UIViewControllerInteractiveTransitioning?

這個(gè)方法是用來支持手勢(shì)驅(qū)動(dòng)的,第一個(gè)參數(shù)同上,animationController當(dāng)前執(zhí)行的動(dòng)畫,返回UIViewControllerInteractiveTransitioning用來控制手勢(shì)交互的對(duì)象


2、UIViewControllerTransitioningDelegate

present和dismiss轉(zhuǎn)場(chǎng)動(dòng)畫,與push和pop的區(qū)別在于沒有operation來區(qū)分是present和dismiss,而是分為兩個(gè)方法,需要各自去實(shí)現(xiàn)

optional public func animationController(forPresented 
    presented: UIViewController, 
    presenting: UIViewController, 
    source: UIViewController)
    -> UIViewControllerAnimatedTransitioning?

presented參數(shù)是被present的vc
source是調(diào)用present的vc
presenting是根控制器,舉個(gè)例子A present B,A又是TabBarController中的一個(gè)vc,那presented就是B,source是A,presenting就是TabBarController,此時(shí)B present C,B沒有TabBarController,所以presented是C,source和presenting就同源都是B

//dismiss同理,沒什么好說的
optional public func animationController(forDismissed 
    dismissed: UIViewController)
    -> UIViewControllerAnimatedTransitioning?
//手勢(shì)驅(qū)動(dòng)動(dòng)畫,和navigation動(dòng)畫一樣
optional public func interactionControllerForPresentation(using 
    animator: UIViewControllerAnimatedTransitioning)
    -> UIViewControllerInteractiveTransitioning?

optional public func interactionControllerForDismissal(using 
    animator: UIViewControllerAnimatedTransitioning)
    -> UIViewControllerInteractiveTransitioning?

3、UIViewControllerAnimatedTransitioning

轉(zhuǎn)場(chǎng)動(dòng)畫的實(shí)現(xiàn),想怎么酷炫就靠他了

//動(dòng)畫時(shí)長(zhǎng)
public func transitionDuration(using 
    transitionContext: UIViewControllerContextTransitioning?)
    -> TimeInterval
//執(zhí)行動(dòng)畫的方法,根據(jù)transitionContext上下文能獲取fromVC和toVC
public func animateTransition(using 
    transitionContext: UIViewControllerContextTransitioning)

4、UIViewControllerContextTransitioning

動(dòng)畫上下文協(xié)議

//通過key獲取fromVC和toVC
guard let fromVC = transitionContext.viewController(forKey: .from) else {
    return
}
guard let toVC = transitionContext.viewController(forKey: .to) else {
    return
}

//發(fā)生轉(zhuǎn)場(chǎng)動(dòng)畫的視圖,add順序可根據(jù)自己業(yè)務(wù)和動(dòng)畫實(shí)現(xiàn)方式變更
let contentView = transitionContext.containerView
contentView.addSubview(toVC.view)
contentView.addSubview(fromVC.view)

//整個(gè)轉(zhuǎn)場(chǎng)過渡必須調(diào)用的完成方法,不然contentView不會(huì)銷毀
//通常更具上下文的transitionWasCancelled判斷是取消動(dòng)畫還是動(dòng)畫完成
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)

5、UIPercentDrivenInteractiveTransition

手勢(shì)百分比協(xié)議

//關(guān)鍵的三個(gè)方法,update根據(jù)百分比播放動(dòng)畫進(jìn)度
open func update(_ percentComplete: CGFloat)
open func cancel()
open func finish()

LVAniamtor

看過一些轉(zhuǎn)場(chǎng)三方實(shí)現(xiàn),通常只實(shí)現(xiàn)了push或pop,要么就只封裝了present和dismiss,能不能兩個(gè)都封裝在一起統(tǒng)一接口調(diào)用呢?試一下吧,就做成了這樣子...


使用方式

1、初始化得到動(dòng)畫對(duì)象

let animator = LVAnimator()

這個(gè)對(duì)象相當(dāng)于動(dòng)畫管理控制器,接收push和present的事件,可根據(jù)fromVC和toVC來分配對(duì)應(yīng)的轉(zhuǎn)場(chǎng)動(dòng)畫


2、push轉(zhuǎn)場(chǎng)簡(jiǎn)單使用

animator.setup { (fromVC, toVC, operation) -> Dictionary<String, Any>? in
    //動(dòng)畫時(shí)長(zhǎng),自定義動(dòng)畫
    return ["duration" : "1", "delegate" : YourPushAnimation()]
}

如不需要自定轉(zhuǎn)場(chǎng)動(dòng)畫,返回nil即可


3、viewWillAppear注冊(cè)代理

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    animator.registerDelegate(vc: self)
}

4、present動(dòng)畫需特別處理

//present的vc
let vc = LVMineVC()
//present轉(zhuǎn)場(chǎng)比較特殊,需將跳轉(zhuǎn)的vc代理指向當(dāng)前動(dòng)畫對(duì)象
animator.registerDelegate(vc: vc)
present(vc, animated: true)

遇到的坑

起初是從push和pop動(dòng)畫開始封裝的,包括手勢(shì)控制動(dòng)畫一切順利,但是加入present和dismiss后一切就不對(duì)了... present轉(zhuǎn)場(chǎng)需要把目標(biāo)vc的transitioningDelegate對(duì)象指向當(dāng)前對(duì)象,所以就有了以下代碼

let vc = LVMineVC()
animator.registerDelegate(vc: vc)
present(vc, animated: true)

另外一個(gè)問題是封裝present和dismiss后,手勢(shì)驅(qū)動(dòng)出了問題,push和pop手勢(shì)動(dòng)畫時(shí),松手動(dòng)畫會(huì)平滑過渡結(jié)束,而present和dismiss則是一閃而過直接跳到結(jié)束狀態(tài),沒有中間平滑過渡的動(dòng)畫了...
這怎么辦...拆開分為兩套方法不是最初的意愿,就一個(gè)字... 正面剛!

最后網(wǎng)上也看了一些資料,用了CADisplayLink解決了

func startLink() {
    if link == nil {
        link = CADisplayLink(target: self, selector: #selector(LVTransitioningDelegateHelper.linkUpdate))
        link?.add(to: RunLoop.current, forMode: .commonModes)
    }
}

func stopLink() {
    link?.invalidate()
    link = nil
}

@objc func linkUpdate() {
    progress += rate
    if progress >= 0.98 {
        stopLink()
        interactive?.finish()
        interactive = nil
    } else {
        interactive?.update(progress)
    }
}

原理就是當(dāng)手勢(shì)取消或結(jié)束時(shí),使用CADisplayLink補(bǔ)過缺失的過渡動(dòng)畫

case .cancelled, .ended:
        if progress > 0.4 {
            startLink()
        } else {
            interactive?.cancel()
            interactive = nil
        }

另外我想present A用A動(dòng)畫,B用B動(dòng)畫,C用系統(tǒng)的怎么辦...

//注意block用要用weak,因?yàn)榛ハ喟?weak var weakSelf = self
animator.setup(panGestureVC: self, transitionAction: { 
    weakSelf?.myAction()
}) { (fromVC, toVC, operation) -> Dictionary<String, Any>? in
    switch operation {
    case .present:
        if toVC is A {
            return ["duration" : "0.4", "delegate" : APresentAnimation()]
        } else if toVC is B {
            return ["duration" : "0.4", "delegate" : BPresentAnimation()]
        } else if toVC is C {
            return nil
        }
    default: break
    }
    return nil
}

更新

2018.9.11 元組改成Dictionary,setup方法支持oc混編

最后

Demo下載地址 https://github.com/grvlv/LVAnimator

最后編輯于
?著作權(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)容