开发过程中,我们往往会需要满足各种各样的跳转Segues的需求,光靠自带的那几个默认的转场似乎无法满足成天充满奇思妙想的产品定位人员(哥,我完全没有恶意。哥你说啥效果我就给你啥效果,你是我亲哥)
AppCoda上有一篇文章其实有专门非常详细的讲述了有关于Custom自定义转场Segues的一个小例子,国内也有译文版本,大概预览了下,自己也照着做了个小的Demo。我调重要的部分做为学习的笔记,也希望能给到看到本篇文章的您一点启发
Demo的主要功能其实很简单,建立三个View Controller,以不同的方式(向上滑动屏幕,向下滑动屏幕,点击按钮)进行自定义转场,当中会用到一些动画去呈现,三个View视图控制器都会根据跳转和返回视图(Segue <-> Unwind)呈现相应的数据。让我们了解转场和解除转场并在这之间进行数据呈现的所有过程。Demo的效果如图
首先在Main.storyboard里面创建好这三个ViewController,并添加三个类对这三个View视图进行绑定。再建立两个完整转场动作的四个自定义类(主视图跳转到第二视图,第二视图回到主视图的segue和unwind处理类。主视图点击按钮跳转到第三视图,第三视图回到主视图的segue和unwind处理类,他们都继承自UIStoryboardSegue),当然也要创建主视图segue连线创建custom转场方式并在转场中写好Identifier、绑定刚刚创建的segue处理类,在主视图的ViewController上创建一个@IBAction方法(解除转场依赖且必须要定义一个这样的方法),同样的segue连线把第二视图对象与Exit连线绑定刚刚创建在主视图里的@IBAction,并设置好Identifier。准备好了这一切,此时你已经设置好了第一视图和第二视图所需要的准备工作,让我们用代码的角度看下,ViewController#1 和 ViewController#2之间互相切换的时候过程是怎么样产生的
1.当app启动的时候,第一个视图viewDidLoad()被执行,向上滑动,创建好的识别器被启动:
var swipeGestureRecognizer:UISwipeGestureRecognizer=UISwipeGestureRecognizer(target:self, action:"showSecondViewController") swipeGestureRecognizer.direction=UISwipeGestureRecognizerDirection.Up self.view.addGestureRecognizer(swipeGestureRecognizer)
2.识别器启动了绑定的方法:
func showSecondViewController() { self.performSegueWithIdentifier("idFirstSegue", sender:self) }
上面的方法体里启动Identifier值为“idFirstSegue”的类(就是我们之前绑定的主视图向第二视图的segue处理类)
segue处理类做的事情就是重载父类UIStoryboardSegue的perform方法,此时此方法被启动:
override func perform() { var firstVCView =self.sourceViewController.view as UIView! var secondVCView =self.destinationViewController.view as UIView! let screenWidth =UIScreen.mainScreen().bounds.size.width let screenHeight =UIScreen.mainScreen().bounds.size.height secondVCView.frame=CGRectMake(0.0, screenHeight, screenWidth, screenHeight) let window =UIApplication.sharedApplication().keyWindow window?.insertSubview(secondVCView, aboveSubview: firstVCView) UIView.animateWithDuration(0.4, animations: { () ->Voidin firstVCView.frame=CGRectOffset(firstVCView.frame,0.0, -screenHeight) secondVCView.frame=CGRectOffset(secondVCView.frame,0.0, -screenHeight) }) { (Finished) ->Voidin self.sourceViewController.presentViewController(self.destinationViewController as! UIViewController, animated:false,completion:nil) } }
上面做的事情其实很简单,我们一行行解读
首先得到源视图窗口对象
得到目标视图窗口对象
得到屏幕的宽和高
指定应该出现视图的初始位置。把视图放在当前视图的正下方,设置frame让他在视图外
得到窗口的子视图对象
把窗口子视图对象放到目标视图
UIView启用animateWithDuration动画运动,修改两个视图的frame,把视图1移除到屏幕上方边缘,同时把视图2放到视图1原来的位置,在后面设置一个挂尾闭包执行显示目标控制器
以上就是一个完整的主图切换到第二视图的过程
我们来看看解除转场又是怎么样的:
我们现在要做的就是解除转场的准备工作:
当解除转场发生的时候,会自动调用我们在ViewController处理类中写好的重载segueForUnwindingToViewController方法,需要注意的是,他有三个参数分别是:fromViewController,toViewController还有identifier,字面意识上就可以解释:当前显示的视图控制器(消失的)、目标视图控制器(要它显示的)、第三个大家都懂(转场行为标示符),代码如下:
override func segueForUnwindingToViewController(toViewController: UIViewController, fromViewController: UIViewController, identifier: String?) -> UIStoryboardSegue { if let id = identifier{ if id == "idFirstSegueUnwind"{ //初始化解除转场自定义类的对象 let unwindSegue = FirstCustomSegueUnwind(identifier: id, source: fromViewController, destination: toViewController , performHandler: { () -> Void in }) return unwindSegue //返回这个解除专场自定义类对象 } }//同样返回此行为对象给父类的super方法 return super.segueForUnwindingToViewController(toViewController, fromViewController: fromViewController, identifier: identifier) }
上面代码上也很好理解,例子是主视图绑定的两个视图之间的跳转,所以需要通过判断identifier来判断返回到哪个解除转场中
完成了解除转场的准备工作,接着就是触发他了:
这个时候我们在第二视图的ViewController中所以,当用户向下滑动屏幕:
var swipeGestureRecognizer:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "showFirstViewController")//定义一个滑动手势,并绑定滑动的action方法是: showFirstViewController()方法 swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Down//滑动的方向,向下 self.view.addGestureRecognizer(swipeGestureRecognizer)//让视图添加识别动作为 (向下滑动) 的识别器
//当向下滑动,执行方法 func showFirstViewController(){ self.performSegueWithIdentifier("idFirstSegueUnwind", sender: self)//执行转场行为,转场行为是identifier里面的id }
(⊙o⊙)… 是不是这个时候觉得,怎么那么眼熟,是的,历史又开始重演
这个时候监听到用户滑动的识别器开始执行第二视图倒回第一视图:Unweid的解除转场处理类
同样跟上面的segue处理类相同,也是重写父类的perform方法
override func perform() { var secondVCView = self.sourceViewController.view as UIView! var firstVCView = self.destinationViewController.view as UIView! let screenHeight = UIScreen.mainScreen().bounds.size.height let window = UIApplication.sharedApplication().keyWindow window?.insertSubview(firstVCView, aboveSubview: secondVCView) UIView.animateWithDuration(0.4, animations: { () -> Void in firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, screenHeight) secondVCView.frame = CGRectOffset(secondVCView.frame, 0.0, screenHeight) }) { (Finished) -> Void in self.sourceViewController.dismissViewControllerAnimated(false, completion: nil) } }
相信大家也都能看出来Unwind唯一和Segue区别的就是:源视图和目标视图相反了(很好理解,就是现在需要第二视图转向第一视图)
animateWithDuration运动现在是把两个视图都同时做底部移动的动作
** 以上就是解除转场Unwind处理类的写法 **
转场运动的时候数据的传输也很简单,其实就是通过重载prepareForSegue这个方法进行的,这个方法会得到一个segue对象,所以我们可以以此segue对象判断是哪个identifier的ID的转场来进行label的显示值操作,代码如下:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "idFirstSegue" { let secondViewController = segue.destinationViewController as! SecondViewController secondViewController.message = "Hello from the 1st View Controller" } }
同样的解除转场时候,也可以在第二视图的处理类中写相同的代码,包括第三个视图的segue和Unwind这里就不再重复了
还有就是第三个视图转换到主视图时候的缩小动画,其实也不是什么很难的东西:
主视图跳转到第三个视图的动画实现:
thirdVCView.transform = CGAffineTransformScale(thirdVCView.transform, 0.001, 0.001) UIView.animateWithDuration(0.5, animations: { () -> Void in firstVCView.transform = CGAffineTransformScale(thirdVCView.transform, 0.001, 0.001) }) { (Finished) -> Void in UIView.animateWithDuration(0.5, animations: { () -> Void in thirdVCView.transform = CGAffineTransformIdentity }, completion: { (Finished) -> Void in firstVCView.transform = CGAffineTransformIdentity self.sourceViewController.presentViewController(self.destinationViewController as! UIViewController, animated: false, completion: nil) }) }
第三个视图解除转场动画实现:
firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, screenHeight) firstVCView.transform = CGAffineTransformScale(firstVCView.transform, 0.001, 0.001) let window = UIApplication.sharedApplication().keyWindow window?.insertSubview(firstVCView, aboveSubview: thirdVCView) UIView.animateWithDuration(0.5, animations: { () -> Void in thirdVCView.transform = CGAffineTransformScale(thirdVCView.transform, 0.001, 0.001) thirdVCView.frame = CGRectOffset(thirdVCView.frame, 0.0, -screenHeight) firstVCView.transform = CGAffineTransformIdentity firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, -screenHeight) }) { (Finished) -> Void in self.sourceViewController.dismissViewControllerAnimated(false, completion: nil) }
值得一提的是,当初我们创建的解除转场绑定@IBAction方法,其实她就是实现第二视图解除转场回到第一视图闪了一下红色背景,并实现第三视图回到第一视图 welcome back那句label的方法:
@IBAction func returnFromSegueActions(sender: UIStoryboardSegue){ if sender.identifier == "idFirstSegueUnwind" { let originalColor = self.view.backgroundColor self.view.backgroundColor = UIColor.redColor() UIView.animateWithDuration(1.0, animations: { () -> Void in self.view.backgroundColor = originalColor }) } else{ self.lblMessage.text = "Welcome back!" } }
在这个IBAction中我们可以窥探并预想到转场之间我们需要和能做到的事情
文章已经看到这里的同学,应该可以完全理解上面的句子的含义
额,结尾了,好长好长,原文其实更长,很多都是重复的,所以我以自己的理解,把运行过程以分段式的方式去解释当中的代码(其实就是偷懒~ 哈哈)
相信也希望以上我的学习总结,能够帮助到你理解自定义转场的所有过程,同时在这个基础上创建更多的可能性