转载

iOS9-自定义转场

参考书籍: iOS 9 by Tutorials

博客原文

其实早在iOS7就推出了两个View之间的自定义过度转变。但是在iOS9中这种自定义转换进一步让你通过自定义 segues 来使过渡动画和视图控制器完全分离。

通过一个小的demo来了解一下吧。

Getting started

初始化代码

一个简单的项目 PamperedPets ,宠物照看的应用程序,完成后讲显示宠物的思想和她们的详细列表。

iOS9-自定义转场

试着探索一下这个初始项目,看他是怎么运行的。

注意:当你打开这个项目的时候在 Storyboard 中会有些警告,不要惊慌。之后会解决的。

看一下 Main.storyboard 它有一些预先创建好的 scenes ,你将开始 Animal Detail and Animal Photo scenes的工作。

iOS9-自定义转场

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-dragTap Gesture Recognizer TO Tap Gesture Recognizer ,从弹出的菜单中选择 present modally

完成上边的步骤就建立好了一个 segue

选择 Animal Detail View ControllerAnimal 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并且点击照片,你将会看到一个大的图片出现。

iOS9-自定义转场

你会发现你回不去了。那么此时你需要创建一个 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

iOS9-自定义转场

重新运行app,就会回发现你从大的图片中返回去了。

我们来探究一下这里发生了什么。当你点击详细视图中的缩略图的时候,手势识别就开始一个 segue modalAnimalDetailViewControllerAnimalPhotoViewControllerAnimalDetailViewController 在这里被称作为 source view controller ,而 AnimalPhotoViewController 责备称作为 destination view controller 。这个 segue 持有 sourcedestination 的引用。

iOS9-自定义转场

在这个过程中,目标视图控制器将会调用 transition delegate 来执行默认的 Cover Vertical 动画。

Your custom segue library

Main.storyboard 中选择 PhotoDetail segue ( the Animal Detail and the Animal Photo view controllers. )改变他的 segue classDropSegue

再次运行项目,你会发现点击照片之后的动画已经完全改变了。

Creating a custom segue

现在你创建一个自己定义的的 segue 去更换 DropSegue 。并且将要创建一个如下图的转场动画.

iOS9-自定义转场

创建一个自定义的seuge最难的部分就是术语,你将要使用的协议名字相当的长。

  • UIViewControllerTransitioningDelegate : 自定义转场使用该协议来完成动画。

  • UIViewControllerAnimatedTransitioning: 该动画对象通过该协议来描述动画。

  • UIViewControllerContextTransitioning: 这个上下文包含有关呈现,并介绍控制器和视图的详细信息;你把它传递给动画对象,为他们提供在其上执行动画的背景下。

在你开始之前,我们先看看创建一个转场的动画都需要那些步骤:

  1. 继承 UIStoryboardSegue 的子类,设置 segue 为目标控制器的委托.

  2. 创建一个展示和消失的 animator

  3. 定义动画效果及其持续时间,在动画中使用。

  4. 指导 segue 用于演示和解雇动画类。

  5. 最后在故事版中使用这个 segue

Subclass UIStoryboardSegue

创建一个 Cocoa Touch Class 文件命名为 ScaleSegue.swift 继承 UIStoryboardSegue

然后扩展这个类

extension ScaleSegue: UIViewControllerTransitioningDelegate { }

ScaleSegue 这个类里重写父类的方法 preform()

override func perform() {   destinationViewController.transitioningDelegate = self   super.perform() }

在这里你设置 destination view controllertransitioning delegateScaleSegue

Create the animator

ScaleSegue.swift 文件下边写如下一个类:

class ScalePresentAnimator : NSObject,  UIViewControllerAnimatedTransitioning { }

你将使用 ScalePresentAnimator 这个类去展现 modal view 。你也将建立一个消失时的动画,但是目前来说,一切都还是使用的默认的动画。需要注意的是Xcode中会抱怨这还不符合UIViewController AnimatedTransitioning 协议;你只是要解决这个问题。

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

iOS9-自定义转场

接下来运行程序你就会看到下边的动画。

iOS9-自定义转场

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)! 下添加如下代码

//获取源视图的控制器和View let 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你就会发现当你单击图片之后,图片就会从原本的位置满满地放大。是不是这样子看起来更加的舒服呢?

iOS9-自定义转场

Working with the view hierarchy

接下来我们做点小的改变来让你的app在iPad上运行起来别具一格。

找到 animateTransition(_:) 这个函数,在` toView?.frame = finalFrame

toView?.layoutIfNeeded()`后边紧跟着写上下边的代码

 fromView?.alpha = 0.0

然后在动画完成的闭包里写上:

 fromView?.alpha = 1.0  transitionContext.completeTransition(true)

接下来在你的iPad中运行你的app,你会看到下边的样子。

iOS9-自定义转场

Handling embedded view controllers

接下来呢我们在 Main.storyboard 中选择 Navigation Controller ,在属性面板中勾选上 Is Initial View Controller: 这一项。

iOS9-自定义转场

现在呢你运行程序你会首先看到一个动物的列表,你任意的点击一个,然后点击图片你会发现。奇怪怎么又变成了从左上角出现的动画了。

iOS9-自定义转场

那是应为我把视图控制器现在嵌入了导航控制器里,使得呈现视图控制器的不是 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)        }
正文到此结束
Loading...