iOS9-自定义转场

参考书籍:iOS 9 by Tutorialsgit

博客原文swift

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

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

Getting started

初始化代码ide

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

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

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

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

What are segues?

Segues描述了场景之间的转换。他们显示为视图控制器场景之间的箭头,有几种类型的 Seguescode

  • 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而且点击照片,你将会看到一个大的图片出现。

你会发现你回不去了。那么此时你须要建立一个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 modalAnimalDetailViewControllerAnimalPhotoViewControllerAnimalDetailViewController在这里被称做为source view controller,而AnimalPhotoViewController责备称做为destination view controller。这个segue持有sourcedestination 的引用。

在这个过程当中,目标视图控制器将会调用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: 这个上下文包含有关呈现,并介绍控制器和视图的详细信息;你把它传递给动画对象,为他们提供在其上执行动画的背景下。

在你开始以前,咱们先看看建立一个转场的动画都须要那些步骤:

  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中会抱怨这还不符合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)!下添加以下代码

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

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,你会看到下边的样子。

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)
       }
相关文章
相关标签/搜索