Swift版抽屉效果,自定义转场动画管理器

效果展示

image

iOS7.0加入了自定义转场动画,淘汰了之前左右两大隐藏护法的抽屉效果,并且一些浮窗、弹层都可以用vc来显示了,不再是用view盖在window上

看了一些抽屉Demo发觉都是OC写的,本篇使用Swift4.0编写一个纯正Swift版转场动画管理器,其中用到元组,OC混编可能需要改为字典... 目前已经支持oc混编


自定义转场动画协议

1、UINavigationControllerDelegate

push和pop转场动画协议,主要用到的方法有两个

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

参数navigationController当前执行动画的导航栏
operation用来判断push还是pop来实现不同动画
fromVC和toVC,A push B,A是fromVC,B是toVC,B pop,B是fromVC,A是toVC,顾名思义没什么好说的
最后返回需要执行的动画

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

这个方法是用来支持手势驱动的,第一个参数同上,animationController当前执行的动画,返回UIViewControllerInteractiveTransitioning用来控制手势交互的对象


2、UIViewControllerTransitioningDelegate

present和dismiss转场动画,与push和pop的区别在于没有operation来区分是present和dismiss,而是分为两个方法,需要各自去实现

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

presented参数是被present的vc
source是调用present的vc
presenting是根控制器,举个例子A present B,A又是TabBarController中的一个vc,那presented就是B,source是A,presenting就是TabBarController,此时B present C,B没有TabBarController,所以presented是C,source和presenting就同源都是B

//dismiss同理,没什么好说的
optional public func animationController(forDismissed 
    dismissed: UIViewController)
    -> UIViewControllerAnimatedTransitioning?
//手势驱动动画,和navigation动画一样
optional public func interactionControllerForPresentation(using 
    animator: UIViewControllerAnimatedTransitioning)
    -> UIViewControllerInteractiveTransitioning?

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

3、UIViewControllerAnimatedTransitioning

转场动画的实现,想怎么酷炫就靠他了

//动画时长
public func transitionDuration(using 
    transitionContext: UIViewControllerContextTransitioning?)
    -> TimeInterval
//执行动画的方法,根据transitionContext上下文能获取fromVC和toVC
public func animateTransition(using 
    transitionContext: UIViewControllerContextTransitioning)

4、UIViewControllerContextTransitioning

动画上下文协议

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

//发生转场动画的视图,add顺序可根据自己业务和动画实现方式变更
let contentView = transitionContext.containerView
contentView.addSubview(toVC.view)
contentView.addSubview(fromVC.view)

//整个转场过渡必须调用的完成方法,不然contentView不会销毁
//通常更具上下文的transitionWasCancelled判断是取消动画还是动画完成
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)

5、UIPercentDrivenInteractiveTransition

手势百分比协议

//关键的三个方法,update根据百分比播放动画进度
open func update(_ percentComplete: CGFloat)
open func cancel()
open func finish()

LVAniamtor

看过一些转场三方实现,通常只实现了push或pop,要么就只封装了present和dismiss,能不能两个都封装在一起统一接口调用呢?试一下吧,就做成了这样子...


使用方式

1、初始化得到动画对象

let animator = LVAnimator()

这个对象相当于动画管理控制器,接收push和present的事件,可根据fromVC和toVC来分配对应的转场动画


2、push转场简单使用

animator.setup { (fromVC, toVC, operation) -> Dictionary? in
    //动画时长,自定义动画
    return ["duration" : "1", "delegate" : YourPushAnimation()]
}

如不需要自定转场动画,返回nil即可


3、viewWillAppear注册代理

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

4、present动画需特别处理

//present的vc
let vc = LVMineVC()
//present转场比较特殊,需将跳转的vc代理指向当前动画对象
animator.registerDelegate(vc: vc)
present(vc, animated: true)

遇到的坑

起初是从push和pop动画开始封装的,包括手势控制动画一切顺利,但是加入present和dismiss后一切就不对了... present转场需要把目标vc的transitioningDelegate对象指向当前对象,所以就有了以下代码

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

另外一个问题是封装present和dismiss后,手势驱动出了问题,push和pop手势动画时,松手动画会平滑过渡结束,而present和dismiss则是一闪而过直接跳到结束状态,没有中间平滑过渡的动画了...
这怎么办...拆开分为两套方法不是最初的意愿,就一个字... 正面刚!

最后网上也看了一些资料,用了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)
    }
}

原理就是当手势取消或结束时,使用CADisplayLink补过缺失的过渡动画

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

另外我想present A用A动画,B用B动画,C用系统的怎么办...

//注意block用要用weak,因为互相包含了
weak var weakSelf = self
animator.setup(panGestureVC: self, transitionAction: { 
    weakSelf?.myAction()
}) { (fromVC, toVC, operation) -> Dictionary? 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

你可能感兴趣的:(Swift版抽屉效果,自定义转场动画管理器)