美文网首页iOS自定义控件与效果ios闲来一看iOS
swift10分钟实现炫酷的导航控制器跳转动画

swift10分钟实现炫酷的导航控制器跳转动画

作者: codeGlider | 来源:发表于2015-08-19 13:50 被阅读7808次
    最终效果
    在开始前需要下载该教程的初始项目,点击下面的地址来下载
    初始项目github地址
    当然你也可以在文章结尾找到完成后的项目,但是我还是建议你一步一步跟着我的教程去编写代码,这样可以更好地掌握它们 : )

    导航控制器的工作原理

    导航控制器
    导航控制器使用一种叫做导航栈的东西来控制导航,用一个由多个视图控制器组成的数组来表示,如下图所示:
    导航栈
    导航控制器能进行两个操作,Push(压栈),Pop(出栈),实际上都是对导航栈进行操作
    • push操作
    导航栈压栈操作
    • pop操作
    导航栈出栈操作

    如何自定义导航栏跳转

    是这样的,UIKIt是通过代理模式来自定义导航控制器跳转动画,每次运行页面跳转动画时,UIKit都会去检查它的UINavigationControllerDelegate代理中的方法func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning
    的返回值,如果是nil的话,就会执行系统默认的动画
    如果返回值是一个实现了UIViewControllerAnimatedTransitioning协议的NSObject,也就是我们将要创建的自定义动画控制器Animator,则会执行我们的自定义动画

    所以总结一下,我们要自定义跳转动画的话需要这么几个步骤:

    1. 创建自定义的动画类,并实现UIViewControllerAnimatedTransitioning协议

    2. 让导航跳转的起始视图控制器和终点视图控制器都实现UINavigationControllerDelegate代理

    3. 具体实现动画内容,主要在UIViewControllerAnimatedTransitioning协议中的animateTransition()方法中实现

    so,我们知道了原理,也知道大概实现的步骤了,下面就开始写代码了!

    开始写代码( ⊙ o ⊙ )。。。

    首先创建我们的自定义动画类RevealAnimator(创建一个RevealAnimator.swift文件)让它成为NSObject的子类,并实现UIViewControllerAnimatedTransitioning协议
    就像这样:

     class RevealAnimator: NSObject,UIViewControllerAnimatedTransitioning {
         
     }
    

    当然你还需要实现该协议的两个必须实现的方法
    transitionDuration()animateTransition()

    首先添加一些必要的变量:

    let animationDuration = 2.0
    
    var operation:UINavigationControllerOperation = .Push
    

    第一个长量用来确定动画的时长(2秒算是比较长的时间,为了是能够更好地观察动画效果);operation变量用来确定当前跳转动画是push还是pop一个视图控制器

    然后再去实现这两个方法,返回我们定义的常量来设置动画的时长

    func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
        return animationDuration
        
    }
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        
        }
    

    然后打开我们的主视图控制器MasterViewController.swift

    在扩展里让当前类遵循导航控制器的代理协议UINavigationControllerDelegate

    记得在viewDidLoad()里将导航控制器的代理设置为当前视图控制器

    navigationController?.delegate = self
    

    下面创建我们自定义动画类的一个实例

    let transition = RevealAnimator()
    

    实现代理的方法,并将我们自定义的动画类实例作为返回值返回

    func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
      transition.operation = operation
        
        return transition
        
    }
    

    这一长串参数的方法是不是很唬人?不用怕,下面我们来详细解释一下这个方法的参数:

    • navigationController:这个参数用来确定当前使用的是哪个导航控制器,由于导航控制器可能有多个,虽然这不是很常见,但是也是有这样的可能性。
    • operation:这是一个UINavigationControllerOperation类型的枚举变量,它只有两个取值:.Push或者.Pop
    • fromVC:这是当前可视的视图控制器,也就是当前导航栈的栈顶视图控制器
    • toVC: 这是你将要跳转到的视图控制器
    • 返回值:返回一个需要执行的动画类,如果你的跳转动画有多个,需要哪个动画执行就返回哪个动画的实例即可。

    不过目前我们的动画类还是一个空壳,so现在去完成它吧~

    实现push动画代码

    打开RevealAnimator.swift,添加一个变量

    weak var storedContext: UIViewControllerContextTransitioning?
    

    因为你要为跳转动画创建一些图层动画,所以你要在动画结束前(也就是代理方法animationDidStop()执行之前)将跳转的上下文保存起来,对其进行一些操作

    在animateTransition()中保存跳转的上下文

    storedContext = transitionContext
    

    下面真正开始添加动画的代码

    • 从跳转上下文中取出跳转开始和目的视图控制器
      let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as! MasterViewController
      let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as! DetailViewController

    • 将跳转目的视图控制器的主视图添加到跳转上下文的容器视图中
      transitionContext.containerView().addSubview(toVC.view)

    • 配置变形的图层动画,将logo上移一段距离并放大到150倍
      let animation = CABasicAnimation(keyPath: "transform")

        animation.fromValue = NSValue(CATransform3D: CATransform3DIdentity)
        
        animation.toValue = NSValue(CATransform3D:
            CATransform3DConcat(//用来合成3D变形动画
                 //向上移动10点
                CATransform3DMakeTranslation(0.0, -10.0, 0.0),
                //x,y方向放大150倍,z方向不变
                CATransform3DMakeScale(150.0, 150.0, 1.0)
            )
        )
            
        animation.duration = animationDuration
        animation.delegate = self
        animation.fillMode = kCAFillModeForwards
        animation.removedOnCompletion = false
        animation.timingFunction = CAMediaTimingFunction(name:
            kCAMediaTimingFunctionEaseIn)
      
    • 同时给遮罩和logo添加变形动画
      toVC.maskLayer.addAnimation(animation, forKey: nil)
      fromVC.logo.addAnimation(animation, forKey: nil)

    • 配置逐渐显现的图层动画
      let fadeInAnimation = CABasicAnimation(keyPath: "opacity")
      fadeInAnimation.fromValue = 0.0
      fadeInAnimation.toValue = 1.0
      fadeInAnimation.duration = animationDuration

    • 给目的视图控制器的视图添加fade-in动画
      toVC.view.layer.addAnimation(fadeInAnimation, forKey: nil)

    • 重写animationDidStop()方法,进行动画的结束操作

      override func animationDidStop(anim: CAAnimation!, finished flag: Bool) {
        
            if let context = storedContext{
        context.completeTransition(!context.transitionWasCancelled())
         //重置logo
            let fromVC = context.viewControllerForKey(UITransitionContextFromViewControllerKey) as! MasterViewController
        fromVC.logo.removeAllAnimations()
            }
            storedContext = nil
           }
      

    好的,到目前为止你的动画类就编写完成了~

    当然我们的遮罩图层还没有进行配置
    打开DetailViewController.swift,在viewDidLoad()方法中配置遮罩图层:

     maskLayer.position = CGPoint(x: view.layer.bounds.size.width/2,y: view.layer.bounds.size.height/2)
     view.layer.mask = maskLayer
    

    将遮罩图层在视图中居中,并将图层的遮罩设置为我们自定义的遮罩,也就是swift logo的形状

    最后在视图控制器完全显现后,除去遮罩图层,在viewDidAppear()中添加如下代码:

    view.layer.mask = nil
    

    到目前为止我们的导航控制器的push动画部分就完成了。

    实现pop动画代码

    pop动画部分就相对比较简单了

    首先用一个if语句将以上push动画部分( )包裹起来:

    if operation == .Push {
    storedContext = transitionContext
       ....................
      toVC.view.layer.addAnimation(fadeInAnimation, forKey: nil)
    }else{
    
    //这里添加pop动画的实现代码
    
    }
    

    在注释处添加一些代码

            let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)! as UIView
            
            let toView = transitionContext.viewForKey(UITransitionContextToViewKey)! as UIView
            
            transitionContext.containerView().insertSubview(toView, belowSubview: fromView)
           
            UIView.animateWithDuration(animationDuration, animations: { () -> Void in
             fromView.transform = CGAffineTransformMakeScale(0.1, 0.1)
                fromView.alpha = 0.0
                
            }, completion: { (finish) -> Void in
        transitionContext.completeTransition(true)
                
            })
    

    这段代码看起来和push部分很像,但是要注意的一点是,pop动画进行的时候是无法对视图控制器进行操作的,只能对他们的视图进行操作:

     let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)! as UIView
            
    let toView = transitionContext.viewForKey(UITransitionContextToViewKey)! as UIView
    

    所以这里取出的是UIView

    这段代码也需要说明一下,这段代码做的是把pop动画的初始视图放在了目的视图之下,因为我们要实现的效果是初始视图缩小消失,最后完全露出目的视图

     transitionContext.containerView().insertSubview(toView, belowSubview: fromView)
    

    现在整个项目应该可以完整地运行了~push动画和pop动画都可以进行

    push pop
    如果你遇到了一些错误,或者是动画效果没有实现,请下载完整项目来检查一下:
    完整项目

    此教程翻译自iOS Animations by Tutorials v1.4 第20章的内容,根据自己的理解做了一些改变,包括绘制swift的logo,关于如何绘制这个logo请继续关注我的文章,下一个教程会告诉你如何快速得到绘制代码!

    如果本篇文章对你有帮助,可以点一下左下角的喜欢,大家的支持与鼓励是继续写作的动力~

    相关文章

      网友评论

      • a4cd7cbb7c8b:我按着你的步骤改,也试过直接下完整版,但是返回按钮怎么没用呢?
      • 许久__:这样手势返回还有效吗?
        a4cd7cbb7c8b:我试过返回按钮不起作用了,我也不知道哪里出问题
      • 巴图鲁:不错
      • 海旭猫:很赞
      • huiwen1208:楼主辛苦啦。写的很详细
      • 夏虫的废话:何必和那种毫无作用的喷子讲道理,楼主辛苦。
      • 毅个天亮:代码没看完就问了个愚蠢的问题,无耻地删了哈哈,谢谢博主还回答了~
        codeGlider:@毅个天亮 😂😂
      • 霜林醉88:那个@2d2c9a634077 别人分享经验的文章你也要喷,还‘一切翻译行为都是自作多情’,貌似你一生下来就会英文似的,你小学英语的课本还不是人家翻译的?凭什么不懂英文就不能学技术?人家阿拉伯语,西班牙语国家的人就不能学技术了?最讨厌这种自我感觉良好的人,不知道哪里来的优越感,还一口一个国内,不想待国内就给我滚!
        codeGlider:@霜林醉88 好样的😁
      • 不知什么人: :grin: 支持下楼主~看个技术博客都能遇到喷子 :pensive:
        codeGlider:@从今以后 不管他们,技术上不如人就在道德上贬低别人
      • 南栀倾寒:什么都不说 支持楼主 别在意喷子
      • aja_xi:“但是我还是建议你一步一步跟着我的教程去编写代码,这样可以更好地掌握它们 : )”。 作者水平有高低文章有好坏,这些都正常且不论;但是教程两字就让我笑出腹肌了,程序员还是应该要点节操的。
      • 学游泳的小黑:老黄、好厉害,顶你!
      • afde2c9f7c90:说到这是国内,目前这类技术文章匮乏,还是感谢up主搬运翻译。
        aja_xi:只会上百度 当然资源匮乏 呵呵
        codeGlider:@匹诺朝 不客气,我还会继续努力的!
      • 屁啊ok:博主不需要理会一些无脑的喷子,无视就好了
        9d135f17e846:@屁啊ok 楼主有分享精神,非常感谢
        codeGlider:@屁啊ok 嗯,没法和这种人讲理
      • 2d2c9a634077:你在国内呆久了就会觉得一切互联网的东西都应该是免费的,包括别人的脑力劳动成果,都相当习以为常的拿来随意使用,记住这是一本技术书同时也是一本商业书,是用来出售的,在分享之前至少要经过愿作者同意,这是对创作者最基本的尊重。
        lfb_CD:@2d2c9a634077 如果不以利益的目的发表或传播就不算侵权。明白?博主翻译一篇文章分享大家有何错?最烦一群出过国就自以为是的家伙,国情就是这样的,你能咋地,中国发展速度就是靠这些上去的好么
        codeGlider:@2d2c9a634077 让更多人了解
        codeGlider:@2d2c9a634077 你以为我是免费看的吗?我是在它的网站上购买来的,我自己理解之后再用自己的话说出来,然后让更多的了解这有什么问题吗?
      • 2d2c9a634077:可以前面要加个不好吗!真是怀疑你的英文水平。
        codeGlider:@大姨夫斯基 您有本事您也翻译一篇啊,等待您的大作!在这里说风凉话算什么本事呢
        d986d284aade:@codeGlider 看到开头说的“教程”,再看看配图, 我以为是心血之作,原来只是翻译呀。评论区好像更有看头,哈哈。
        codeGlider:@2d2c9a634077 这个算是我翻译错了,但是你说的我不赞同,你看哪个技术论坛没有翻译的技术分享嘛,好的东西为什么不能分享
      • 2d2c9a634077:我实在是受不了了,你对那个版权的翻译完全反了意思好吗
      • b1e6145c6034:看了,代码在哪编😳😳还是不怎么会
        codeGlider:没问题,下载的话点进去那个网址然后点击Download Zip就可以了 :blush:
        b1e6145c6034:@codeGlider 还没,我下载看看,不会能再问你吗?
        codeGlider:@蒙太奇 你下载初始项目代码了吗
      • 2d2c9a634077:我觉得这些有版权的文章还是少翻译为好,希望尽量原创而不是一直抄袭国外的东西。
        codeGlider:我只笑笑,不说话 :blush:
        2d2c9a634077:@codeGlider 个人不认为学技术的人不懂英文,个人认为不懂英文的人不应该学技术,一切翻译行为都是自作多情。
        codeGlider:@2d2c9a634077 翻译是为了让不懂英文的同学也能学习到前沿的技术,而且你知道抄袭的定义吗?
        抄袭:指窃取他人的作品当作自己的,在相同的使用方式下,完全照抄他人作品和在一定程度上改变其形式或内容的行为。我只是把我学习这个项目的经验用自己的话描述出来,完全不构成抄袭、你说我抄袭了代码?对不起,软件注释已经明确指出,该软件所有人都可以免费获得其副本,和没有限制的权限去使用、复制、修改、合并、发布、许可、和/或出售该软件的副本。
      • b66f10112a6f:沙发,手捧可乐薯片
        codeGlider:@b66f10112a6f 谢谢支持~

      本文标题:swift10分钟实现炫酷的导航控制器跳转动画

      本文链接:https://www.haomeiwen.com/subject/oslwqttx.html