自己定义View Controller转换动画
- 原文链接 : Introduction to Custom View Controller Transitions and Animations
- 原文作者 : joyce echessa
- 译文出自 : 开发技术前线 www.devtf.cn
- 译者 : kmyhy
观察 iOS 自带的 App,你会看到当你从一个视图导航到还有一个视图时总是会显示各种各样的转换动画,以“主-从”视图为例(相似的程序有Messages App或者系统设置程序),一个轻扫动作能够让详情视图呈如今主视图之上,在呈现一个新的 View Controller 时还会带有丰富的转换动画。
iOS 7 新增的自己定义 View Controller 转换动画,使让开发人员在 App 中发生从一个 View Controller 到还有一个 View Controller 的导航时,创建自己定义的动画效果。在本教程中。我们将学习这个内容。此外。我们还会学习怎样通过手势来发起一个导航,也就是所谓的“交互式导航”。
在開始之前,请下载本教程中用到的 dl=0">開始项目
開始
创建自己定义转换动画的,能够分成 3 个步骤进行:
指定一个类,实现 UIViewControllerAnimatedTransitioning 协议。在这个类中,我们编写执行动画的代码。这个类充当动画控制器。
在呈现一个 View Controller 之前,设置这个 View Controller 的 transitioningDelegate 属性为某个对象。这个对象在呈现这个 View Controller 的过程中将被调用。用于获取转场时应该使用什么对象作为动画控制器。
实现回调方法。用于返回一个你在第一歩中创建的动画控制器对象。
执行開始项目,你将看到一个列表。在导航栏上有一个 Action button。点击它,它将呈现一个新的 View Controller(以 modal 方式从底部向上弹起)。我们将为这个转换动作自己定义转换动画。
Custom Present Transition
实现 View Controller 呈现动画
如前面所述。首先须要创建动画控制器。新建一个 NSObject 子类 CustomPresentAnimationController,声明将遵守 UIViewControllerAnimatedTransitioning 协议:
class CustomPresentAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
UIViewControllerAnimatedTransitioning protocol has two required methods which we’ll add next. Add the following methods to the class.
UIViewControllerAnimatedTransitioning 协议有两个必须实现的方法,我们来实现它们:
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 2.5
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let finalFrameForVC = transitionContext.finalFrameForViewController(toViewController)
let containerView = transitionContext.containerView()
let bounds = UIScreen.mainScreen().bounds
toViewController.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.size.height)
containerView.addSubview(toViewController.view)
UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.0, options: .CurveLinear, animations: {
fromViewController.view.alpha = 0.5
toViewController.view.frame = finalFrameForVC
}, completion: {
finished in
transitionContext.completeTransition(true)
fromViewController.view.alpha = 1.0
})
}
第一个方法用于指定动画播放的时长。对于本例。我们指定为 2.5 秒。当然在真正的 App 你能够把这个数字调小一些。
第二个方法带有一个 transitionContext 參数,通过这个对象,我们能够获取转换过程中涉及的 from 控制器(開始控制器)、to 控制器(到达控制器)、当动画完毕后的 final frame (to 视图的最终位置及大小),以及 containerView——这个容器用于包括 from 控制器和 to 控制器的 UIView。
然后。我们将 to 视图放在屏幕的下方,并将 to 视图加入到 containerView。
在动画块中,我们将 to 视图移动到 final frame 的位置。同一时候将 from 视图的 alpha 值设置为 0.5。这样,当 to 视图向上滑入的同一时候 from 视图淡出。
执行动画块时。第一个參数调用了transitionDuration(transitionContext:)方法,用这种方法的返回值作为动画块的执行时间。当动画完毕,调用完毕块,我们在完毕块中通知 transitionContext 动画已经完毕,同一时候将 from 视图的 alpha 值设回 1.0。
写好动画控制器类之后,我们须要在故事板中将动画控制器分配给某个 segue。
打开 ItemsTableViewController.swift ,改动类声明例如以下:
class ItemsTableViewController: UITableViewController, UIViewControllerTransitioningDelegate {
UIViewController 有一个 transitionDelegate 属性,用于指定自己定义转换动画。当转场到一个 View Controller 时,框架会使用这个属性进行转换。而 UIViewControllerTransitioningDelegate 属性则负责提供自己定义转换对象。
打开 Main.storyboard 选择导航到 Action View Controller 的那条 segue,在属性面板将它的 Identifier 设置为 showAction。
回到 ItemsTableViewController ,加入例如以下代码:
let customPresentAnimationController = CustomPresentAnimationController()
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showAction" {
let toViewController = segue.destinationViewController as UIViewController
toViewController.transitioningDelegate = self
}
}
这里我们创建了一个我们的动画控制器对象。然后在 prepareForSegue() 方法中,找到 ID 为 showAction 的 segue,设置它的 to 视图控制器的 transitioningDelegate 属性。
然后在这个类中实现 UIViewControllerTransitioningDelegate 方法。并在这些方法中返回我们的自己定义动画控制器。
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning?
{
return customPresentAnimationController
}
执行程序,你能够看到 Action 视图从屏幕底部缓缓弹起,并在停止前呈阻尼运动。
假设想看看其它效果,在 CustomPresentAnimationController.swift 中找到例如以下语句:
toViewController.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.size.height)
改动为:
toViewController.view.frame = CGRectOffset(finalFrameForVC, 0, -bounds.size.height)
这将让 to 视图从屏幕上方向下滑入。
执行 App 效果例如以下:
自己定义解散动画
前面我们自己定义了呈现动画,但当视图解散时。仍然使用的是默认的动画效果。
UIViewControllerTransitioningDelegate 协议还同意我们指定一个动画控制器作位解散动画。就如同呈现时我们所做的一样。我们接下来就来实现它。
创建一个 NSObject 子类 CustomDismissAnimationController,改动其类声明例如以下:
class CustomDismissAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
加入例如以下方法:
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 2
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let finalFrameForVC = transitionContext.finalFrameForViewController(toViewController)
let containerView = transitionContext.containerView()
toViewController.view.frame = finalFrameForVC
toViewController.view.alpha = 0.5
containerView.addSubview(toViewController.view)
containerView.sendSubviewToBack(toViewController.view)
UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
fromViewController.view.frame = CGRectInset(fromViewController.view.frame, fromViewController.view.frame.size.width / 2, fromViewController.view.frame.size.height / 2)
toViewController.view.alpha = 1.0
}, completion: {
finished in
transitionContext.completeTransition(true)
})
}
事实上这和呈现动画的实现真的非常像。在 animateTransition() 方法中。我们获取 to/from 视图控制器。
在这里。to 控制器变成了表格控制器。
我们改动了 to 视图在动画正式開始之前的 alpha 值。然后将 to 视图加入到 containerView,并将它放到 from 视图的后面,以便它在一開始的时候不可见。
在动画块中,我们让 from 视图的大小变成 0,0。但中心位置不变。这将导致 from 视图由大变小直至消失。同一时候将 to 视图的 alpha 值设置为 1.0 使其可见。
在 ItemsTableViewController 中加入几个属性声明:
let customDismissAnimationController = CustomDismissAnimationController()
然后加入例如以下方法:
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return customDismissAnimationController
}
UIViewControllerTransitioningDelegate 协议的这种方法返回一个动画控制器作为解散控制器。
执行效果例如以下:
动画效果不是我们所预料的。你能够看到 from 视图的白色框架确实是如预期的缩小了,但图片的尺寸根本不会改变。这是由于只改变视图的 frame 并不会影响它的 subviews。我们能够用 UIView 的截屏功能解决问题。
UIView 截屏功能会对一个 UIView 进行截图,将它绘制在一个“轻量级”的 UIView 中。
我们将在淡入淡出动画中使用截屏进行动画,而不是直接用真正的视图进行动画。
将 animateTransition() 方法代码改动为:
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let finalFrameForVC = transitionContext.finalFrameForViewController(toViewController)
let containerView = transitionContext.containerView()
toViewController.view.frame = finalFrameForVC
toViewController.view.alpha = 0.5
containerView.addSubview(toViewController.view)
containerView.sendSubviewToBack(toViewController.view)
let snapshotView = fromViewController.view.snapshotViewAfterScreenUpdates(false)
snapshotView.frame = fromViewController.view.frame
containerView.addSubview(snapshotView)
fromViewController.view.removeFromSuperview()
UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
snapshotView.frame = CGRectInset(fromViewController.view.frame, fromViewController.view.frame.size.width / 2, fromViewController.view.frame.size.height / 2)
toViewController.view.alpha = 1.0
}, completion: {
finished in
snapshotView.removeFromSuperview()
transitionContext.completeTransition(true)
})
}
这里,我们创建了 from 视图的截屏,将截屏加到 containerView,然后将 from 视图从 containerView 中移除。
如今,动画效果最终正常了。
定制导航控制器转换动画
面看到,要在呈现一个模式窗体的过程中呈现自己定义动画,我们须要让负责呈现的 View Controller 实现一个 Transitioning 协议。可是,为每个 View Controller 都指定一个托付对象还是太麻烦了,尤其是当我们在使用 UITabBarController 或 UINavigationController 的时候。
对于这两种控制器。有一种更简单的办法,能够使用 UITabBarControllerDelegate 或者 UINavigationControllerDelegate 协议来实现自己定义转换动画。
我们来看一下自己定义导航控制器转换动画的样例。
首先,创建一个动画控制器。新建一个 NSObject 子类 CustomNavigationAnimationController。改动其类声明例如以下:
class CustomNavigationAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
然后编写例如以下代码。这里我准备实现一个简单的魔方动画。
这个动画控制器和前面实现的两个动画控制器相似。
注意 reverse 变量,我们用这个变量来指定动画的方向,也就是说要进行的动画是主视图->从视图,抑或相反方向。
var reverse: Bool = false
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 1.5
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView()
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let toView = toViewController.view
let fromView = fromViewController.view
let direction: CGFloat = reverse ?
-1 : 1
let const: CGFloat = -0.005
toView.layer.anchorPoint = CGPointMake(direction == 1 ?
0 : 1, 0.5)
fromView.layer.anchorPoint = CGPointMake(direction == 1 ? 1 : 0, 0.5)
var viewFromTransform: CATransform3D = CATransform3DMakeRotation(direction * CGFloat(M_PI_2), 0.0, 1.0, 0.0)
var viewToTransform: CATransform3D = CATransform3DMakeRotation(-direction * CGFloat(M_PI_2), 0.0, 1.0, 0.0)
viewFromTransform.m34 = const
viewToTransform.m34 = const
containerView.transform = CGAffineTransformMakeTranslation(direction * containerView.frame.size.width / 2.0, 0)
toView.layer.transform = viewToTransform
containerView.addSubview(toView)
UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
containerView.transform = CGAffineTransformMakeTranslation(-direction * containerView.frame.size.width / 2.0, 0)
fromView.layer.transform = viewFromTransform
toView.layer.transform = CATransform3DIdentity
}, completion: {
finished in
containerView.transform = CGAffineTransformIdentity
fromView.layer.transform = CATransform3DIdentity
toView.layer.transform = CATransform3DIdentity
fromView.layer.anchorPoint = CGPointMake(0.5, 0.5)
toView.layer.anchorPoint = CGPointMake(0.5, 0.5)
if (transitionContext.transitionWasCancelled()) {
toView.removeFromSuperview()
} else {
fromView.removeFromSuperview()
}
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
})
}
打开 ItemsTableViewController.swift,将类声明改动为:
class ItemsTableViewController: UITableViewController, UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
UINavigationControllerDelegate 协议负责提供动画控制器对象。
加入例如以下属性:
let customNavigationAnimationController = CustomNavigationAnimationController()
在 viewDidLoad 方法最后加入:
navigationController?.delegate = self
这句代码将导航控制器的 delegate 设置为 self,这样 ItemsTableViewController 就必须实现新的转换托付方法。
新增例如以下方法:
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
customNavigationAnimationController.reverse = operation == .Pop
return customNavigationAnimationController
}
这种方法会在导航控制器发生导航时调用,导航控制器会要求这种方法返回一个动画控制器实例。
转换的方向依据动画的类型(Push或Pop)而定。
执行程序。点击表格单元格,效果例如以下:
交互式转换
我们将让上述转换变成“交互式转换”,这样用户能够用手势来进行视图控制器的转换。
iOS 有很多 App 支持这个特性。通过交互式转换,你能够从左向右滑动来替代返回button的功能。你还能够用小幅度的轻扫手势查看 master 视图的内容,然后中途取消转换。
大幅度的轻扫手势则是进行 Pop 导航操作。
首先,须要创建一个交互式控制器。交互式控制器须要实现 UIViewControllerInteractiveTransitioning 协议。UINavigationControllerDelegate 或 Transitioning 托付在请求完一个动画控制器之后还会请求一个交互式控制器。
接下来创建交互式控制器。
新建一个 UIPercentDrivenInteractiveTransition 子类,名为 CustomInteractionController。
UIPercentDrivenInteractiveTransition 类已经实现了 UIViewControllerInteractiveTransitioning 协议,因此我们的类就没有必要再声明对这个协议的实现了。
对于 UIPercentDrivenInteractiveTransition 子类,我们必须使用单独的 UIView 动画,以便动画能够被停止、返回和播放。
继续加入例如以下代码:
var navigationController: UINavigationController!
var shouldCompleteTransition = false
var transitionInProgress = false
var completionSeed: CGFloat {
return 1 - percentComplete
}
func attachToViewController(viewController: UIViewController) {
navigationController = viewController.navigationController
setupGestureRecognizer(viewController.view)
}
private func setupGestureRecognizer(view: UIView) {
view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: "handlePanGesture:"))
}
func handlePanGesture(gestureRecognizer: UIPanGestureRecognizer) {
let viewTranslation = gestureRecognizer.translationInView(gestureRecognizer.view!.superview!)
switch gestureRecognizer.state {
case .Began:
transitionInProgress = true
navigationController.popViewControllerAnimated(true)
case .Changed:
var const = CGFloat(fminf(fmaxf(Float(viewTranslation.x / 200.0), 0.0), 1.0))
shouldCompleteTransition = const > 0.5
updateInteractiveTransition(const)
case .Cancelled, .Ended:
transitionInProgress = false
if !shouldCompleteTransition || gestureRecognizer.state == .Cancelled {
cancelInteractiveTransition()
} else {
finishInteractiveTransition()
}
default:
println("Swift switch must be exhaustive, thus the default")
}
}
attachToViewController方法中。引用了一个导航控制器并保存到实例变量。以便当手势发生时用于初始化 Pop 转换。然后在视图中加入了一个手势识别器,并将手势处理方法指定为 handlePanGesture()方法。手势处理方法将处理手势的每个状态,包括:
開始:将 transitionInProgress 设置为 true,然后開始进行 Pop 导航。
改变:这个状态表示手势尚处于进行过程中,因此须要计算出转换的进度(百分比)。
这里假设扫动距离>= 200像素视同手势100%的完毕。假设小于此距离则计算手势划过的距离占200像素的百分之几。并以此作为动画完毕的进度。同一时候我们检查用户在释放手指前,划过的距离是否超过了一半,并将推断结果保存到 shouldCompleteTransition 变量。
取消/结束:将 transitionInProgress 设置为 false。同一时候推断 shouldCompleteTransition 变量是否为 false 或者手势已取消,假设是则取消动画,否则让动画完毕。
用一个计算属性来衡量完毕速度。
UIPercentDrivenInteractiveTransitio 有一个 completionSeed 属性,这个属性用于告诉框架当手势已经完毕时,还剩下多少动画须要播放。
当手势取消时,假设这个数值越大。则 View Controller 弹回去的速度就越快。
接下来就是使用这个交互式控制器。
打开 ItemsTableViewController.swift,加入例如以下属性:
let customInteractionController = CustomInteractionController()
在 navigationController(_:animationControllerForOperation:fromViewController:toViewController:)方法開始部分加入:
if operation == .Push {
customInteractionController.attachToViewController(toVC)
}
假设是 Push 动画,则调用 CustomInteractionController 的 attachToViewController() 方法,将 toViewController 传递给动画控制器。
然后加入例如以下方法:
func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
{
return customInteractionController.transitionInProgress ? customInteractionController : nil
}
当框架请求一个动画控制器之时,它也会用上面的这种方法请求一个交互式控制器。
在这种方法中,我们推断是否转换開始,假设是。则返回一个交互式控制器给它。
执行 App ,查看转换动画效果:
结论
我们学习了怎样创建一个自己定义的 View Controller 转换,以及怎样创建交互式转换。通过这些特性,开发人员能够在 App 中彻底控制从一个视图控制器切换到还有一个视图控制器时的转换动画。并通过创建一系列独特的体验来取悦他们的用户。你能够从此下载完整的演示样例项目。
注意: 本教程有一个中文版本号(台湾繁体)。我们还将支持很多其它语言。
假设你想參加我们的翻译小组。请和我们联系。
自己定义View Controller转换动画的更多相关文章
- Swift:超炫的View Controller切换动画
匿名社交应用Secret的开发者开发了一款叫做Ping的应用,用户可以他们感兴趣的话题的推送. Ping有一个很炫的东西,就是主界面和之间切换的动画做的非常的好.每次看到一个非常炫的动画,都不由得会想 ...
- View Controller容器
在 iOS 5 之前,view controller 容器是 Apple 的特权.实际上,在 view controller 编程指南中还有一段申明,指出你不应该使用它们.Apple 对 view c ...
- 【IOS笔记】View Controller Basics
View Controller Basics 视图控制器基础 Apps running on iOS–based devices have a limited amount of screen s ...
- iOS7之定制View Controller切换效果
在iOS5和iOS6前,View Controller的切换主要有4种: 1. Push/Pop,NavigationViewController常干的事儿 2. Tab,TabViewControl ...
- MVC(Model View Controller)框架
MVC框架 同义词 MVC一般指MVC框架 MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一 ...
- 用Swift完成不同View Controller之间的切换
之前用objective-c开发时,页面之间的切换很容易.其实用swift没有很大的变化,如果你是用storyboard完成的界面,基本上是同样的方式,只不过在代码部分写成swift风格的就行了. 今 ...
- Android 它们的定义View它BounceProgressBar
转载请注明出处:http://blog.csdn.net/bbld_/article/details/41246247 [Rocko's blog] 之前几天下载了非常久没用了的桌面版酷狗来用用的时候 ...
- Android自己定义view之measure、layout、draw三大流程
自己定义view之measure.layout.draw三大流程 一个view要显示出来.须要经过測量.布局和绘制这三个过程,本章就这三个流程具体探讨一下.View的三大流程具体分析起来比較复杂,本文 ...
- iOS 自己定义页面的切换动画与交互动画 By Swift
在iOS7之前,开发人员为了寻求自己定义Navigation Controller的Push/Pop动画,仅仅能受限于子类化一个UINavigationController,或是用自己定义的动画去覆盖 ...
随机推荐
- Python-数据类型-转摘
1.数字 2 是一个整数的例子.长整数 不过是大一些的整数.3.23和52.3E-4是浮点数的例子.E标记表示10的幂.在这里,52.3E-4表示52.3 * 10-4.(-5+4j)和(2.3-4. ...
- [Bayesian] “我是bayesian我怕谁”系列 - Variational Autoencoders
本是neural network的内容,但偏偏有个variational打头,那就聊聊.涉及的内容可能比较杂,但终归会 end with VAE. 各个概念的详细解释请点击推荐的链接,本文只是重在理清 ...
- CSS系列------选择器和选择器的优先级
1.1.基本选择器 通配符选择器(*) 通配符选择器的使用方法如下 *{margin:0px; padding:0px;} //*代表所有的 ID选择器(#) ID选择器的使用方式如下: * ...
- linq中group by
本文导读:LINQ定义了大约40个查询操作符,如select.from.in.where.group 以及order by,借助于LINQ技术,我们可以使用一种类似SQL的语法来查询任何形式的数据.L ...
- C语言的第一个程序 “hello world!”
1,C语言的简介 C语言是一门通用计算机编程语言,应用广泛.C语言的设计目标是提供一种能以简易的方式编译.处理低级存储器.产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言. ...
- C语 三子棋小游戏
#include <stdio.h> #include <Windows.h> #include<time.h> #define row 3 #define lis ...
- 想使用Docker容器?先看看这些注意事项
Docker容器无疑是最近十年来最引人注目的技术之一,因为有了它,对我们思考设计.开发和运维软件的方式产生了非常有益的影响. 但是就像每一个开发工具一样,为了充分利用这些工具,需要注意一些使用中问题, ...
- linux 运维常用工具表
https://code.google.com/p/httperf/ ※测量Web服务器的性能 ./configure make &&make install http://ww ...
- 07深入理解Java线程池
之前面试baba系时遇到一个相对简单的多线程编程题,即"3个线程循环输出ADC",自己答的并不是很好,深感内疚,决定更加仔细的学习<并发编程的艺术>一书,到达掌握的强度 ...
- ubuntu16.04安装交叉编译链
我使用的是arm-linux-gcc 4.3.2版本,其他版本类似,附上下载链接: https://pan.baidu.com/s/1geUOfab 密码: frzy 首先我的安装包是tar.bz2的 ...