参考书籍:iOS 9 by Tutorials
其实早在iOS7就推出了两个View之间的自定义过度转变。但是在iOS9中这种自定义转换进一步让你通过自定义segues
来使过渡动画和视图控制器完全分离。
通过一个小的demo来了解一下吧。
Getting started
一个简单的项目PamperedPets
,宠物照看的应用程序,完成后讲显示宠物的思想和她们的详细列表。
试着探索一下这个初始项目,看他是怎么运行的。
注意:当你打开这个项目的时候在
Storyboard
中会有些警告,不要惊慌。之后会解决的。
看一下Main.storyboard
它有一些预先创建好的scenes
,你将开始Animal Detail
and Animal Photo
scenes的工作。
What are segues?
Segues
描述了场景之间的转换。他们显示为视图控制器场景之间的箭头,有几种类型的 Segues
Show : Pushes a scene from a navigation controller.
Show Detail: Replaces a scene detail when in a UISplitViewController
Present Modally: Presents a scene on top of the current scene.
Popover: Presents a scene as a popover on the iPad or full screen on the iPhone.
这篇文章我们仅仅自定义modal segues
A simple segue
在Main.storyboard
里选择Animal Detail View Controller
,从Object Library
拖拽出一个Tap Gesture Recognizer
放在Pet Photo Thumbnail
上。
接下来,Ctrl-drag
从Tap Gesture Recognizer
TO Tap Gesture Recognizer
,从弹出的菜单中选择present modally
完成上边的步骤就建立好了一个segue
。
选择 Animal Detail View Controller
和Animal Photo View Controller
之间的segue
.奖identifier
设置为PhotoDetail
在AnimalDetailViewController.swift
中重写prepareForSegue(_:sender:)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "PhotoDetail" { let controller = segue.destinationViewController as! AnimalPhotoViewController controller.image = imageView.image }}
现在你运行app并且点击照片,你将会看到一个大的图片出现。
你会发现你回不去了。那么此时你需要创建一个unwind segue
。在AnimalDetailViewController.swift:
中添加如下代码:
@IBAction func unwindToAnimalDetailViewController( segue:UIStoryboardSegue) { // placeholder for unwind segue}
对于一个简单的返场,在这个方法里你不需要添加任何的代码。 任何类似于这样的方法 @IBAction func methodName(segue:UIStoryboardSegue)
都会被认为是Storyboard segue 的 unwind
在Main.storyboard
中选择Animal Photo View Controller scene.
。从Object Library
拖出来一个Tap Gesture Recognizer
放在Pet Photo View
上。接下来,Ctrl-drag
从你的Tap Gesture Recognizer
TO Exit
,之后从弹出的菜单中选择unwindToAnimalDetailViewController
重新运行app,就会回发现你从大的图片中返回去了。
我们来探究一下这里发生了什么。当你点击详细视图中的缩略图的时候,手势识别就开始一个segue modal
从AnimalDetailViewController
到AnimalPhotoViewController
。AnimalDetailViewController
在这里被称作为source view controller
,而AnimalPhotoViewController
责备称作为destination view controller
。这个segue
持有source
和destination
的引用。
在这个过程中,目标视图控制器将会调用transition delegate
来执行默认的Cover Vertical
动画。
Your custom segue library
在Main.storyboard
中选择 PhotoDetail segue
( the Animal Detail and the Animal Photo view controllers.
)改变他的segue class
为DropSegue
再次运行项目,你会发现点击照片之后的动画已经完全改变了。
Creating a custom segue
现在你创建一个自己定义的的segue
去更换DropSegue
。并且将要创建一个如下图的转场动画.
创建一个自定义的seuge最难的部分就是术语,你将要使用的协议名字相当的长。
UIViewControllerTransitioningDelegate : 自定义转场使用该协议来完成动画。
UIViewControllerAnimatedTransitioning: 该动画对象通过该协议来描述动画。
UIViewControllerContextTransitioning: 这个上下文包含有关呈现,并介绍控制器和视图的详细信息;你把它传递给动画对象,为他们提供在其上执行动画的背景下。
在你开始之前,我们先看看创建一个转场的动画都需要那些步骤:
继承
UIStoryboardSegue
的子类,设置segue
为目标控制器的委托.创建一个展示和消失的
animator
类定义动画效果及其持续时间,在动画中使用。
指导
segue
用于演示和解雇动画类。最后在故事版中使用这个
segue
Subclass UIStoryboardSegue
创建一个Cocoa Touch Class
文件命名为ScaleSegue.swift
继承UIStoryboardSegue
。
然后扩展这个类
extension ScaleSegue: UIViewControllerTransitioningDelegate {}
在ScaleSegue
这个类里重写父类的方法preform()
override func perform() { destinationViewController.transitioningDelegate = self super.perform()}
在这里你设置destination view controller
的transitioning delegate
是 ScaleSegue
。
Create the animator
在ScaleSegue.swift
文件下边写如下一个类:
class ScalePresentAnimator : NSObject, UIViewControllerAnimatedTransitioning {}
你将使用ScalePresentAnimator
这个类去展现modal view
。你也将建立一个消失时的动画,但是目前来说,一切都还是使用的默认的动画。需要注意的是Xcode中会抱怨这还不符合UIViewControllerAnimatedTransitioning
协议;你只是要解决这个问题。
Define the animation
ScalePresentAnimator
遵从UIViewControllerAnimatedTransitioning
,你需要实现这个协议所必需的一些方法。
func transitionDuration( transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 2.0}//规定动画持续的时间(一般时间比较短,这里设置的比较长,是为了明显的看到效果)
实际的动画效果:
func animateTransition(transitionContext: UIViewControllerContextTransitioning){ // 1 获取到目标视图的控制器和View let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let toView = transitionContext.viewForKey(UITransitionContextFromViewKey) // 2 添加 toView到transiton的context if let toView = toView{ transitionContext.containerView()?.addSubview(toView) } //3 目标视图的初始状态是在屏幕左上角大小为零的一个矩形,当你更改视图的 Frame 时总是要去调用`layoutIfNeeded`来更新视图的约束 toView?.frame = .zero toView?.layoutIfNeeded() //4 这个动画必报就是把那个大小为零的矩形View变成最终的大小的一个动画 let duration = transitionDuration(transitionContext) let finalFrame = transitionContext.finalFrameForViewController(toViewController) UIView.animateWithDuration(duration, animations: { () -> Void in toView?.frame = finalFrame toView?.layoutIfNeeded() }) { (_) -> Void in //5 transitionContext要在动画结束时清理,调用completeTransition transitionContext.completeTransition(true) } }
Set the animator in the segue
在UIViewControllerTransitioningDelegate
下添加下边这个方法。
extension ScaleSegue:UIViewControllerTransitioningDelegate{ func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return ScalePresentAnimator() }}这是简单的在告诉`segue`在展现下一个view的时候使用你的`ScalePresentAnimator`动画
Use the segue in the storyboard
接下来在Main.storyboard
中将PhotoDetail segue
更换成ScaleSegue
,同时呢,改变Presentation
成为Form Sheet
接下来运行程序你就会看到下边的动画。
Passing data to animators
通过协议来传递数据。在ScaleSegue.swift
里建立一个 protocol
protocol ViewScaleable{ var scaleView:UIView{get}}
通过扩展AniamalDetailViewController
继承ViewScaleable
协议
在AniamalDetailViewController.swift
中添加下边的代码
extension AniamalDetailViewController:ViewScaleable{ var scaleView: UIView {return imageView}}
在ScaleSegue.swift
文件中找到animateTransiton
这个函数,在let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
下添加如下代码
//获取源视图的控制器和Viewlet fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)
将toView?.frame = .zero
替换为
var startFrame = CGRect.zero if let fromViewController = fromViewController as? ViewScaleable{ startFrame = fromViewController.scaleView.frame }else{ print("Warning: Controller \(fromViewController) does not"+"conform to ViewScaleable") } toView?.frame = startFrame
现在你重新运行你的app你就会发现当你单击图片之后,图片就会从原本的位置满满地放大。是不是这样子看起来更加的舒服呢?
Working with the view hierarchy
接下来我们做点小的改变来让你的app在iPad上运行起来别具一格。
找到animateTransition(_:)
这个函数,在` toView?.frame = finalFrame
fromView?.alpha = 0.0
然后在动画完成的闭包里写上:
fromView?.alpha = 1.0 transitionContext.completeTransition(true)
接下来在你的iPad中运行你的app,你会看到下边的样子。
Handling embedded view controllers
接下来呢我们在Main.storyboard
中选择 Navigation Controller
,在属性面板中勾选上Is Initial View Controller:
这一项。
现在呢你运行程序你会首先看到一个动物的列表,你任意的点击一个,然后点击图片你会发现。奇怪怎么又变成了从左上角出现的动画了。
那是应为我把视图控制器现在嵌入了导航控制器里,使得呈现视图控制器的不是AnimalDetailViewController
那很简单我们来解决一下就好了。
我们在ScaleSegue.swift
这个文件里找到,
let fromViewController = transitionContext .viewControllerForKey( UITransitionContextFromViewControllerKey)!
将这句代码改为:
var fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! if let fromNC = fromViewController as? UINavigationController{ if let controller = fromNC.topViewController{ fromViewController = controller } }
此刻你重新运行代码就会恢复原样喽。
当你嵌入了一个UITaBarController
处理情况也是类似的。
Completing the scale segue dismissal
你会发现如果再次点击大图,大图消失的时候的动画还是之前的默认情况。我们接下来就完成消失时的动画吧。其实呢既然已经完成了presenting animator
那么dismiss animator
就简单了许多了吧。道理是一样的,那你就挑战一下自己吧。完成接下来的任务!
修改下边这个段代码
if let toView = toView{ transitionContext.containerView()?.addSubview(toView) }
修改为
if let toView = toView,fromView = fromView{ transitionContext.containerView()?.insertSubview(toView, belowSubview: fromView) }