美文网首页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:@毅个天亮 😂😂
  • c971ac3e135c:那个@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