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请继续关注我的文章,下一个教程会告诉你如何快速得到绘制代码!