效果展示

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