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<String, Any>? 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<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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 概述 这篇文章,我将讲述几种转场动画的自定义方式,并且每种方式附上一个示例,毕竟代码才是我们的语言,这样比较容易上...
    伯恩的遗产阅读 53,608评论 37 379
  • 前言的前言 唐巧前辈在微信公众号「iOSDevTips」以及其博客上推送了我的文章后,我的 Github 各项指标...
    VincentHK阅读 5,241评论 4 44
  • 概述 这篇文章,我将讲述几种转场动画的自定义方式,并且每种方式附上一个示例,毕竟代码才是我们的语言,这样比较容易上...
    iOS_Developer阅读 988评论 0 4
  • 准备: 苹果在iOS7之后,提供了自定义转场API。使得我们可以对模态(present、dismiss)、导航控制...
    M_慕宸阅读 1,310评论 0 7
  • 更新,更简单的自定义转场集成! 几句代码快速集成自定义转场效果+ 全手势驱动 写在前面 这两天闲下来好好的研究了一...
    wazrx阅读 73,707评论 84 583