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

最终效果

在开始前需要下载该教程的初始项目,点击下面的地址来下载
初始项目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请继续关注我的文章,下一个教程会告诉你如何快速得到绘制代码!

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

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

推荐阅读更多精彩内容

  • 在iOS开发中,界面间的跳转其实也就是控制器的跳转,跳转有很多种,最常用的有push,modal. modal:任...
    Dariel阅读 6,649评论 11 84
  • 前言的前言 唐巧前辈在微信公众号「iOSDevTips」以及其博客上推送了我的文章后,我的 Github 各项指标...
    VincentHK阅读 5,360评论 3 44
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,092评论 4 62
  • 对于mvp模式,我之前也有过简单了解,知道他的核心思想是解耦,但一直没有怎么深入学习。总觉得,这种模式需要创建太多...
    wanghai328阅读 365评论 0 1
  • 西秦木子抒情诗集《守望者》目录 青果,一年一轮回 但只在童年扎根 在心里,好像初恋 无意中相遇 成为终生唏嘘的缘 ...
    西秦木子阅读 480评论 0 6