原文:http://www.cocoachina.com/ios/20150126/11011.htmlhtml
iOS 5发布的时候,苹果针对应用程序界面的设计,提出了一种全新的,革命性的方法—Storyboard,它从根本上改变了现有的设计理念。iOS 5以前,每一个视图控制器一般都伴有一个Interface Builder的文件,叫nib或者xib,这个想法比较简单:每一个视图控制器的界面应该在各自的nib文件中设计,而全部的nib文件一块儿构成了整个应用程序的界面。一方面,这个是很方便的,由于它强迫开发者在界面设计的时候将注意力集中在界面上,但另外一方面,到最后,太多的文件不得不被建立,开发者将不能概览应用的总体界面。ios
随着storyboard的产生,上面的这些都成为了历史,由于这种新方法受到了开发者社区的普遍使用。相比老的技术,storyboards提供了三个重要的优点:编程
整个界面设计只发生在一个文件里。项目的总文件数量大大减小了,特别是在大项目里。固然你可使用额外的nib文件,而且容许只建立辅助视图。swift
开发者能即时浏览应用的界面和流程。闭包
视图控制器之间的转换(界面设计的专业术语叫场景(scene)),以及转换是如何发生的,在storyboard中已被完美地定义并清楚地呈现给了开发者。app
综上所述,场景之间的转换构成storyboard的特殊部分,咱们通常把它叫作转场(segue)。框架
转场跟应用的导航和处理是密切相关的,由于它明肯定义了一个视图切换到另外一个视图的转换细节。这些细节指定了是否应用动画,动画的类型,固然还有实际转换时的准备和性能。除此以外,转场也用来将传递数据到下一个视图控制器里,这个用法也很常见。编程语言
从编程的角度看,场景是UIStoryboardSegue类的一个对象,它第一次在iOS 5中介绍到。和其它类的对象不一样的是,这种对象不能直接的建立或使用。不过你能够指定转场的属性,而后在转场即将发生时提供给它以达到目的。UIKit框架提供了一些带默认动画过渡的预约义的转场,包括:push segues(包括导航控制器的app),带有动画选择的模态转场(modal segues), popover segues。更高级的状况下,iOS SDK默认的转场可能不够用,因此开发者必须实现他们的自定义转场(custom segues)。ide
建立一个自定义转场并不难,由于它是iOS标准编程技术的组成部分。实际上你只须要生成UIStoryboardSegue的子类,并重载一个叫perform的方法便可。这个perform方法中必须实现自定义动画的逻辑。从一个视图控制器转换到另外一个以及返回操做的触发,也须要由开发者编程提供,这是一个标准的步骤。工具
在本教程中,个人目标是向大家展现如何实现自定义转场,并经过一个简单的演示应用介绍这个概念的全部方面。拥有建立自定义转场的知识, 能够将你导向开发更强大的app的道路。此外,对于最大化用户体验,并开发引人注目的漂亮应用,自定义转场也颇有帮助。
若是你有兴趣学习我刚刚说的话,就一块儿来探索教程里的全部细节和自定义转场的奥秘吧。
不像我以前几个教程提供了一个启动项目,本教程咱们将从头开始建立app。事实上,我是故意这么作的,由于,项目中一些重要部分须要用到Interface Builder,因此我认为从头开始循序渐进的来作,能让你看清里面的细节。
正如我先前所说,咱们将开发一个很是简单的app,在这个应用中咱们将建立两个自定义转场。须要提早说明的是,咱们的演示应用将有三个视图控制器,也就是在Interface Builder中有三个场景和三个相关类。默认状况下,第一个是由Xcode建立的,所以咱们只要再添加两个。咱们将建立的自定义转场用来导航第一个视图控制器到第二个(以及返回),以及从第一个到第三个(以及返回)。第二个和第三个视图控制器之间咱们不添加任何联系。
所以,咱们须要建立两个自定义转场。由于要包括返回,每个转场须要建立两个对应的类(所以,共四个):第一个类里咱们将实现从第一个视图控制器到另外一个转换的全部自定义逻辑。第二个类实现返回到第一个视图控制器的逻辑,或者换句话说要实现解除转场(unwind segue)。后面会讲到解除转场,如今只须要记住这就是用来让咱们返回到前一个视图控制器的转场。
视图控制器自己没什么须要作的。咱们会用一个label注明视图控制器的名称,每个会有一个不一样的背景颜色,可让咱们很容易地查看转换(是的,这将是一个五光十色的应用)。第一个和第二个视图控制器也会多一个label,其中从其余视图控制器传来的自定义的消息将被显示出来。
最后,转场将在如下的动做发生的时候被触发:
从第一个视图控制器转换到第二个,咱们将使用向上滑动的手势(swipe up).而返回,使用向下滑动的手势(swipe down)。我不打算描述咱们实现的动画,我会展现给大家看。
从第一个视图控制器转换到第三个,咱们用一个按钮(UIButton)。而返回,使用向上滑动手势。对于动画很少作说明。
固然,咱们也会在视图控制器之间传递数据。
在咱们继续以前,有个最终demo,将展现咱们将要作的,以及咱们的自定义转场是如何工做的。
注意,建立一个自定义转场的时候,你能够实现任何一个你想到的或者须要的动画。咱们作的时候,你能够尝试作下。
咱们开始启动Xcode,选择建立一个新工程。在出现的向导中,选择Single View Application做为工程的模板。选择下一步,在Product Name字段里,设置CustomSegues做为项目名称。同时,确保选择的编程语言是Swift,设备是iPhone。
选择下一步。这步中,选择保存项目的路径,而后点击生成按钮。
如今工程已经准备好了,选择工程导航对应的组。默认状况下,General选项卡是打开的,这也是咱们须要的。在Deployment Info部分,不选择Device Orientation区域里的Landscape Left 和 Landscape Right选项,只选择Portrait选项。若是你要在一个iOS 8以前的设备上测试,自由改变从8.1到以前的任意iOS版本deployment target。(最好不要运行在iOS 8以前的设备上)
如今,咱们准备好继续进行实现,经过第一个界面建立开始。
第一步是添加咱们在界面里要用到的视图控制器。因此,在工程导航栏(Project Navigator)里点击Main.storyboard文件,让Interface Builder显示出来。在三个不一样的部分,咱们将打破咱们的工做,每个里咱们将设置一个视图控制器。但首先,要确保改变当前的尺寸类到Compact Width 和Regular Height,这样界面适配iPhone的尺寸。
当打开Interface Builder的时候,你会看到Xcode默认添加的场景。这就是咱们开始的地方。在这个场景里添加两个UILabel对象。第一个设置属性以下:
Frame: X=16, Y=77, W=368, H=21
Font: System Bold
Font size: 24
Text: View Controller #1
Text alignment: Center
同时,设置上下左右的约束条件:
给第二个label,设置以下属性:
Frame: X=16, Y=390, W=368, H=21
Text: None
Text alignment: Center
而后设它的Horizontal Center in Container, Vertical Center in Container),宽和高的约束条件。
完成两个label的属性和约束条件设置以后,在场景中添加一个UIButton对象。参数设置以下:
Frame: X=169, Y=712, W=62, H=30
Title: Tap Me!
Text Color: Black
关于它的约束条件,简单地点击Interface Builder右下角的大头针按钮,选择Selected Views部分里的Add Missing Contrains选项。
你完成以后,选择视图,改变背景颜色,打开Attributes Inspector里对应的下拉菜单,选择“Other…”选项。在颜色选择器里,点击调色板按钮而后选择蜡笔*调色板(由于比较柔和一点),关闭颜色选择器。
下面是第一个场景的截图:
如今关注第二个视图控制器。在画布上直接从库里拖一个新的控制器对象。而后也添加两个UILabel对象,并设置和上面同样的属性。别忘了设置约束条件。这边惟一不一样的地方是第一个label的文本,改成“View Controller #2!”(不带引号)。
下一步,选择视图,再次打开颜色选择器,此次选择Honeydew(列表中第二个),而后关闭颜色选择器。
这时,Xcode将给你个警告,由于两个视图控制器之间没有转场(segue)。不要紧,咱们立刻就要解决这个问题了。
下面这个图展现了第二个场景:
最后,咱们从对象库里再添加一个视图控制器到画布上。此次,只添加一个UILabel对象,不是两个。设置和前两个场景中第一个label同样的属性。有两个不一样点:第一,设置label的framde的Y值为389;第二,设置文本为“View Controller #3!”。这个视图里没有其余子视图了,你只要选择一个背景颜色。和以前两次同样,在颜色选择器里选择sky颜色。
对于它的约束条件,和以前两个视图控制器里的第二个label同样设置 Horizontal Center in Container, Vertical Center in Container,以及宽和高。设置Y值和约束条件,让label正好显示在视图中间。
界面中第三个场景看起来是这样的:
咱们以前添加到界面上的每个场景,必须设置一个不一样的类来实现每个细节。默认状况下,ViewController设定为每一个场景的类,这很是适合咱们的第一个视图控制器(这个是Xcode自动建立的)。而其余两个,咱们须要建立两个新类,下面就让咱们来实现一下。
第一步是建立新的类文件,到Xcode菜单里File>New>File…,启动这个过程,引导建立新文件的细节出现了,因此第一步要确保选择iOS下,Source类别里的Cocoa Touch Class。
点击下一步按钮。在向导的第二步,在Subclass of里:字段选择或输入UIViewController的值。而后指定SecondViewController做为你刚刚建立的名字。固然你不能改变编程语言,确保最后字段里选择的是Swift语言。
点击下一步,生成新的文件。一旦完成,你会看到一个叫SecondViewController.swift的新文件,出如今Project导航器里。
重复上述过程,给第三个视图控制器添加类文件。类名设置为ThirdViewController,剩下的步骤都同样。
添加完第二个文件后,你应该能在Project导航器里看到这两个文件:
咱们回到Interface Builder和新建立场景的类以前,让咱们先声明两个后面要用到的IBOutlet的属性,在ViewController.swift里添加以下属性:
1
|
@IBOutlet?weak?
var
?lblMessage:?UILabel!
|
在SecondViewController.swift文件里,添加和上面彻底同样的设置。这两个属性被链接到开始的两个试图控制器里的第二个label上,稍后咱们能够显示一条消息给它们。
如今再次打开Main.storyboard文件,选择第二个场景(绿色背景那个)。这个上面点击视图控制器对象,进到工具面板里的Identity inspector里,在Custom Class部分,具体在Class字段,设置咱们在工程开始时候添加的第一个类:SecondViewController:
第三个场景(蓝色背景的)也同样的处理。选择后,点击视图控制器的上面,设置ThirdViewController做为它的类:
好了,如今每一个场景匹配了一个不一样的类。在这部分的最后,咱们链接两个以前声明的IBOutlet属性,从第一个视图控制器(橙色背景的那个)开始。点击视图控制器对象的上面,按住Ctrl拖向场景中的那个label:
在弹出的小窗口里,选择lblMessage属性,这样联系就完成了。
在SecondViewController场景里同样的步骤,关联那个视图控制器的lblMessage属性到场景中的label上。
自定义转场的建立包括UIStoryboardSegue的子类化,以及一个必须实现的perform方法。方法中,视图控制器转换的自定义逻辑实际是在这里应用,因此,在写转场相关的代码以前,让咱们先添加全部必要的类。
工程中添加新类的方法以前已经详细介绍过了,因此你须要添加文件的时候也能够将它当成指南。在我给大家每一个文件的细节以前,咱们一共要建立4个文件。由于每一个自定义转场须要两个类,咱们将建立两个文件。具体地说,第一个类用来实现全部的逻辑,以及转场执行的转换动画。第二个类用来实现从第二个视图控制器到第一个的相反动做。也就是所谓的解除转场(unwind segue),咱们稍后将详细介绍。
综上所述,开始向项目中添加新类。添加每一个文件时注意两个问题:老是选择Cocoa Touch Class模板,以及向导中第二步的字段,设置Subclass of的值为UIStoryboardSegue。
咱们将四个文件的名字命名为:
FirstCustomSegue
FirstCustomSegueUnwind
SecondCustomSegue
SecondCustomSegueUnwind
这四个文件加完后都显示在Project导航器里了。
如今让咱们开始第一个自定义转场。写代码以前,必须在Interface Builder里添加转场,给咱们想应用自定义转换的地方联系两个场景。本例中,咱们将建立的转场是从ViewController场景(橙色背景)到SecondViewController场景(绿色背景)。
再次打开Main.storyboard,简单地建立新转场(Segue),扩大文档大纲,若是它被隐藏的话,选择ViewController场景里的ViewController对象:
而后,按住Ctrl将SecondViewController拖向下面的SecondViewController场景中:
关于新的转场,弹出个有不少选项的黑色弹出框。其中有个叫custom:
点击它,转场就立马创建了。你能够经过检查画布中是否添加了一根链接两个场景的连线进行验证。
咱们开始写代码以前还有些设置要完成。首先,点击选择画布上的转场,打开Utilities面板里的属性检查器(Attributes inspector)。咱们要作得最重要的一个任务是,设置转场的identifier值,在代码中会用到这个以便咱们引用。真正重要的是不要给多个转场指定相同的标示符。因此,本例中,设置idFirstSegue为转场的标示符。同时在Segue Class字段里,必须指定咱们用来执行自定义转换的自定义类。这里,咱们设置FirstCustomSegue为自定义转场的类。运行的时候,程序会执行这个类里存在的代码,让转场正常执行。
如今新的自定义转场有了,它的设置也指定好了,接下来让咱们实现第一个视图控制器到第二个控制器的理想的转换。简而言之,写关于自定义转场的代码一般要作的是,“玩”两个视图控制器的视图。首先,目标视图控制器的视图的初始状态是被指定的,而且被手动添加到应用的窗口上。而后使用动画目标视图被放到想要放的最终位置,源视图控制离开屏幕的时候也是动画的。
注意:转场对象是指将被当作目标视图控制器呈现的视图控制器,而当前的视图控制器就是源视图控制器(source view controller)。
最后,当动画转换结束的时候,新的视图控制器将直接呈现给用户,没有更多的动画。
app概述部分的教程中,你已经看到了咱们想要达到的效果。无论转场怎么执行,咱们但愿第一个视图控制器的视图是向上滑动,第二个视图控制器的视图跟着动。两个视图之间没有空隙,第二个把第一个给“粘住”了。
如今让咱们开始写代码,咱们一步一步的看。最后,我会一块儿提供如下方法的全部实现。
打开FirstCustomSegue.swift文件,添加如下方法定义:
1
2
3
|
override?func?perform()?{
}
|
这个方法已经在UIStoryboardSegue的父类里定义了,这里咱们重载这个方法,以便咱们添加想要的自定义逻辑。因此,第一步是使咱们的工做更简单,它指定视图的源和目标视图控制器的两个局部变量,以下所示:
1
2
3
4
5
6
|
override?func?perform()?{
????
//?Assign?the?source?and?destination?views?to?local?variables.
????
var
?firstVCView?=?self.sourceViewController.view?as?UIView!
????
var
?secondVCView?=?self.destinationViewController.view?as?UIView!
}
|
另外,咱们须要屏幕的宽和高,而后放在两个变量里:
1
2
3
4
5
6
7
8
|
override?func?perform()?{
????...
????
//?Get?the?screen?width?and?height.
????let?screenWidth?=?UIScreen.mainScreen().bounds.size.width
????let?screenHeight?=?UIScreen.mainScreen().bounds.size.height
}
|
好了,如今指定应该出现的视图的初始位置。考虑到咱们想要视图从底部移动到上面,咱们把视图放在屏幕可视区域的外面,当前视图的正下方。这很简单,咱们简单地设置下视图的frame:
1
2
3
4
5
6
7
|
override?func?perform()?{
????...
????
//?Specify?the?initial?position?of?the?destination?view.
????secondVCView.frame?=?CGRectMake(0.0,?screenHeight,?screenWidth,?screenHeight)
}
|
这时,第二个视图控制器的视图还不是窗口的子视图。因此,在咱们实现真正的动画以前,很明显,咱们必须把它加到窗口上。这用窗口对象的方法insertSubview(view:aboveSubview:)来实现。正如你接下来看到的,咱们先访问窗口对象,而后添加目标视图:
1
2
3
4
5
6
7
8
|
override?func?perform()?{
????...
????
//?Access?the?app's?key?window?and?insert?the?destination?view?above?the?current?(source)?one.
????let?window?=?UIApplication.sharedApplication().keyWindow
????window?.insertSubview(secondVCView,?aboveSubview:?firstVCView)
}
|
别被上面的话给搞糊涂了。关于窗口子视图,他们的顺序看起来像在栈里同样。通常而言它不会带来麻烦。
最后,让咱们给转换过程添加动画。首先,咱们将第一个视图控制器的视图从屏幕上方移出边缘,同时,把第二个视图放到第一个的位置上。实际上,所谓的“移动”,就是修改两个视图的frame:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
override?func?perform()?{
????...
????
//?Animate?the?transition.
????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
????}
}
|
上面两行代码将执行预期的效果。然而,咱们还没彻底结束,由于咱们还没显示出新的视图控制器(目标视图控制器)。咱们将在第二个闭包里实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
override?func?perform()?{
????...
????
//?Animate?the?transition.
????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.presentViewController(self.destinationViewController?as?UIViewController,
????????????????animated:?
false
,
????????????????completion:?nil)
????}
}
|
动画部分到这里就结束了。总结一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
override?func?perform()?{
????
//?Assign?the?source?and?destination?views?to?local?variables.
????
var
?firstVCView?=?self.sourceViewController.view?as?UIView!
????
var
?secondVCView?=?self.destinationViewController.view?as?UIView!
????
//?Get?the?screen?width?and?height.
????let?screenWidth?=?UIScreen.mainScreen().bounds.size.width
????let?screenHeight?=?UIScreen.mainScreen().bounds.size.height
????
//?Specify?the?initial?position?of?the?destination?view.
????secondVCView.frame?=?CGRectMake(0.0,?screenHeight,?screenWidth,?screenHeight)
????
//?Access?the?app's?key?window?and?insert?the?destination?view?above?the?current?(source)?one.
????let?window?=?UIApplication.sharedApplication().keyWindow
????window?.insertSubview(secondVCView,?aboveSubview:?firstVCView)
????
//?Animate?the?transition.
????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.presentViewController(self.destinationViewController?as?UIViewController,
????????????????animated:?
false
,
????????????????completion:?nil)
????}
}
|
在你给app第一次调试以前,咱们还必须执行自定义转场。这个发生在ViewController视图控制器的视图上作向上滑动的手势。因此,继续打开ViewController.swift文件。在viewDidLoad方法里建立一个新的手势对象,而后把它加到视图里:
1
2
3
4
5
6
7
|
override?func?viewDidLoad()?{
????...
????
var
?swipeGestureRecognizer:?UISwipeGestureRecognizer?=?UISwipeGestureRecognizer(target:?self,?action:?
"showSecondViewController"
)
????swipeGestureRecognizer.direction?=?UISwipeGestureRecognizerDirection.Up
????self.view.addGestureRecognizer(swipeGestureRecognizer)
}
|
当手势被执行的时候,showSecondViewController方法应该是被调用的,但还没实现。所以,让咱们添加它的定义,这里咱们将添加简单的一行代码来执行自定义转场:
1
2
3
|
func?showSecondViewController()?{
????self.performSegueWithIdentifier(
"idFirstSegue"
,?sender:?self)
}
|
能够注意到,咱们是经过标示符来访问转场的。当咱们滑动到视图,以上的调用使咱们在FirstCustomSegue类里的自定义实现得以执行。
你如今能够调试app了。你能够在模拟器上运行,也能够在真机上运行。不过,即便你能够向上滑动来转换视图,你不能返回到第一个视图控制器。这是正常的,由于咱们尚未定义,咱们接下来将实现这个方法。
建立自定义转场的时候,须要同时实现导航里的两个方法:从源视图控制器到目标视图控制器,而后返回。几乎不可能只创建一个方法。如今咱们已经实现了转场,因此它将按咱们想要的动画方式显示第二个视图控制器,咱们必须让app可以作相反的事情。执行返回导航的转场,叫解除转场(unwind segue)。
简而言之,解除转场和正常的同样,可是它的配置有点复杂。某些步骤仍是要完成的,但没有超出标准编程技术以外的东西。注意两点:
解除转场(unwind segue)一般和正常自定义转场(segue)一块儿出现。
要解除转场起做用,咱们必须重写perform方法,并应用自定义逻辑和咱们想要的或者应用规定的动画。另外,导航返回源视图控制器的过渡效果不须要和对应的正常转场相同。
解除转场的实现分四个步骤:
IBAction方法的建立,该方法在解除转场被执行的时候会选择地执行一些代码。这个方法能够有你想要的任何名字,并且不强制包含其它东西。它须要定义,但能够留空,解除转场的定义须要依赖这个方法。
解除转场的建立,设置的配置。这和以前咱们在Interface Builder里的转场不太同样,等下咱们将看看这个是怎么实现的。
经过重写UIStoryboardSegue子类里的perform方法,来实现通常的逻辑。
UIViewController类提供了特定方法的定义,因此系统知道解除转场即将执行。
如今你能够会有些迷糊,接下来,咱们会解释全部的东西。
先从上面提到的第一个步骤里的IBAction方法开始。这个方法一般在咱们想要导航的初始视图控制器里实现。如我所说,方法里能够是空的,可是你得记住若是你不定义至少一个这样的方法,你将没法继续。当解除转场存在在app中,不必使用那么多的方法。一般状况下,简单地定义一个这样的动做方法,检测对应的解除转场对应而后调用,就足够了。
如今行动,打开ViewController.swift文件,添加以下代码:
1
2
3
|
@IBAction?func?returnFromSegueActions(sender:?UIStoryboardSegue){
}
|
咱们将稍后添加些示例代码。注意这个方法的参数是一个UIStoryboardSegue对象,这个是须要记住的细节。
如今让咱们去Interface Builder里,打开Main.storyboard文件。是时候建立解除转场了。咱们开始检查SecondViewController场景里包含的对象,甚至查看场景自己,或者文档大纲。若是你仔细观察,你会发现一个对象,叫Exit:
若是你还没建立过自定义转场,极可能你不会用到它,可能会想知道这个干什么的。这个就是你须要建立的解除转场。
点击场景里SecondViewController对象,按Ctrl拖到Exit对象:
下面的弹出框会显示咱们早前定义的IBAction方法:
确保选中它。解除转场将被建立,你能够在文档大纲里查看到。如今新的联线被建立了,但转场在这里:
如今点击咱们刚建立的解除转场,打开属性检查器。设置“idFirstSegueUnwind”做为转场的标示符:
解除转场的第二部分目前已经完成了。接下来咱们要开始写代码了,并肯定解除转场的执行方法。
首先,打开FirstCustomSegueUnWind.swift文件,再次重写perform方法,并保持对视图的局部变量的引用。同时,咱们也要在一个变量中保存屏幕高度:
1
2
3
4
5
6
7
|
override?func?perform()?{
????
//?Assign?the?source?and?destination?views?to?local?variables.
????
var
?secondVCView?=?self.sourceViewController.view?as?UIView!
????
var
?firstVCView?=?self.destinationViewController.view?as?UIView!
????let?screenHeight?=?UIScreen.mainScreen().bounds.size.height
}
|
注意,如今源视图控制器是SecondViewController,目标视图控制器是ViewController。继续,这里咱们不指定任何视图的初始状态,咱们只把第一个视图控制的视图(咱们想要返回的那个)加到窗口的子视图上:
1
2
3
4
5
6
7
|
override?func?perform()?{
????...
????let?window?=?UIApplication.sharedApplication().keyWindow
????window?.insertSubview(firstVCView,?aboveSubview:?secondVCView)
}
|
最后,咱们必须激活两个视图的运动。此次,他们都往底部移动:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
override?func?perform()?{
????...
????
//?Animate?the?transition.
????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)
????}
}
|
经过改变两个视图的y值,咱们设法把他们在预约义动画的时间内放到新的位置上。当转换结束,咱们只要让第二个视图控制器直接消失,不带任何动画,而后就结束了。下面一块儿给你这个方法的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
override?func?perform()?{
????
//?Assign?the?source?and?destination?views?to?local?variables.
????
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)
????
//?Animate?the?transition.
????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)
????}
}
|
目前为止还好,咱们成功建立了初始化要求的IBAction方法和解除转场,实现了动画转换执行的自定义逻辑。如今,还剩最后一个标准方法须要实现,那个方法里,咱们会使用FirstCustomSegueUnwind类。
打开ViewController.swift文件,定义一下方法:
1
2
3
|
override?func?segueForUnwindingToViewController(toViewController:?UIViewController,?fromViewController:?UIViewController,?identifier:?String?)?->?UIStoryboardSegue?{
}
|
这是UIViewController类提供的方法。在解除转场执行的时候会自动调用这个方法,其实现是手动添加的。有三个参数:
fromViewController: 当前显示的视图控制器,是咱们想让它消失的。
toViewController: 目标视图控制器,或者换句话说是咱们想显示的控制器。
identifier: 将被执行的转场的标示符。
注意,这个方法返回的是一个转场对象。咱们要作的很简单:咱们会用identifier的值来决定要执行的那个转场,而后初始化一个解除转场自定义类的对象,这个类对象是咱们最后要返回的。同时,调用一个super的方法,在这里重载下,让咱们看看代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
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
????????}????????
????}
????
return
?
super
.segueForUnwindingToViewController(toViewController,?fromViewController:?fromViewController,?identifier:?identifier)
}
|
首先,咱们要确认identifier参数有实际的值,因此用if let语句。而后用idFirstSegueUnwind值来确认这个转场是咱们想要的,在if语句中初始化FirstCustomSegueUnwind类对象。这个对象在if语句中返回,外面咱们返回调用super类以后同一个方法返回的值。
如今解除转场准备好了。剩下实现的是触发的它的手势。打开SecondViewController.swift文件,直接到viewDidLoad方法里。添加以下代码:
1
2
3
4
5
6
7
8
|
override?func?viewDidLoad()?{
????...
????
var
?swipeGestureRecognizer:?UISwipeGestureRecognizer?=?UISwipeGestureRecognizer(target:?self,?action:?
"showFirstViewController"
)
????swipeGestureRecognizer.direction?=?UISwipeGestureRecognizerDirection.Down
????self.view.addGestureRecognizer(swipeGestureRecognizer)
}
|
上面定义的手势是向下滑动的手势,恰好与以前的相反。
如今定义showFirstViewController方法:
1
2
3
|
func?showFirstViewController()?{
????self.performSegueWithIdentifier(
"idFirstSegueUnwind"
,?sender:?self)
}
|
如今一切都准备好了,你能够再次调试app。此次,你能够在视图控制器之间来回导航。
前面的部分,咱们声明了一个空方法:returnFromSegueActions(sender:), 咱们说这个方法是必须的,这样咱们才能建立解除转场。同时,我也说事后面会添加一些代码,如今咱们就要添加代码了。
在这个操做方法中,咱们准备作一个很简单的任务:改变第一个视图控制器的背景颜色,而后用UIView的动画把它恢复到正常状态。虽然这个操做在真正的app里面毫无心义,但这是个好的例子,可让咱们看到解除转场运行的时候是怎么操做的。
到ViewController.swift文件,在IBAction方法里,添加以下内容:
1
2
3
4
5
6
7
8
9
10
|
@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
????????})
????}
}
|
首先检查这个解除转场是否是咱们感兴趣的那个,而后把背景颜色保存在本地,改为红色,而后使用动画部分的块,设回到原来的值。
以上清楚地展现了当一个自定义解除转场被执行的时候你想执行动做的时候你应该怎么操做。稍后咱们继续在上面的方法里添加一些代码。
在实现自定义转场(和解除转场)的时候,要注意两点:一,注意你设置的转换效果,以使你的app提供良好的的用户体验。二,执行通常的转场或者解除转场的时候,视图控制器之间的数据交换。
前面部分咱们已经涉及到了第一种状况,咱们指定了两个视图来回切换的动画转换。如今咱们要看看是怎么传递数据的。极可能你已经在UIKit预约义的转场里用到了这个流程。
在咱们开发的演示app中,咱们不会在一个视图控制器之间传递重要的数据。咱们将简单的发送一个字符串值给对方,会在咱们早先在ViewController和SecondViewController场景里添加的第二个标签上显示。
首先,打开SecondViewController.swift文件。添加如下的在IBOutlet属性以后的属性声明:
1
|
var
?message:?NSString!
|
接下来,咱们要用这个属性,从ViewController视图到转场执行到的视图控制器,传递一个字符串值。
打开ViewController.swift文件,在类里,添加如下方法的实现:
1
2
3
4
5
6
|
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"
????}
}
|
上面方法是UIViewController类的一部分。基于咱们要执行的转场的identifier,咱们访问目标视图控制器,也就是本例中的SecondViewController。而后,给SecondViewController类里的message属性设置字符串消息。
以上都是你在使用转场的时候须要传递数据到另外一个视图控制器所须要的。 是由很简单的方法构成的。
如今再次打开SecondViewController.swift文件。是时候处理message属性,和展现上面接收到的字符串。在viewDidLoad方法里,添加以下代码:
1
2
3
4
5
|
override?func?viewDidLoad()?{
????...
????lblMessage.text?=?message
}
|
最后,从第二个视图控制到第一个发送一个字符串值。让咱们再次实现上面的方法:
1
2
3
4
5
6
|
override?func?prepareForSegue(segue:?UIStoryboardSegue,?sender:?AnyObject?)?{
????
if
?segue.identifier?==?
"idFirstSegueUnwind"
?{
????????let?firstViewController?=?segue.destinationViewController?as?ViewController
????????firstViewController.lblMessage.text?=?
"You?just?came?back?from?the?2nd?VC"
????}
}
|
上面的实现中,注意咱们是直接访问ViewController类里的消息label的。这是展现视图控制器子视图数据的另外一个方法。请注意,这两种状况下有必要经过转场对象访问目标视图控制器。
这几乎就是在视图控制器之间传递数据的全部步骤了。咱们一会建立的第二个自定义转场,你会看到最后一种方法,但这里介绍的是你主要用到的。
若是你须要的话能够再运行app。此次,两个视图控制器里地第二个label显示了咱们传递的字符串值。注意,咱们在SecondViewController类里的工做结束了,如今开始咱们将关注工程里存在的第三个视图控制器。
以前部分咱们讨论到的全部概念,都是你在建立和管理自定义转场里须要的。然而,我认为再建立一个可让一切更容易消化理解,咱们也能够看到一些不一样的东西。全部,让咱们开始吧。
第一步,要在Interface Builder里建立自定义转场,因此在工程导航器里的Main.storyboard文件里打开它。
界面再次出如今屏幕上,打开文档大纲板(若是是闭合的话)。点击ViewController场景里的ViewController对象,经过按住Ctrl和鼠标,拖动到ThirdViewController场景里的ThirdViewController对象。将会出现一个蓝色的链接线:
在弹出的窗口里,选择custom转场。作完以后,确保咱们刚建立的转场在你的画布中看起来是这样的:
点击上面的线,在工具板里打开属性检查器。这里你要指定两个东西:转场标示符和它的类。对标示符,在Identifier字段里设置idSecondSegue值。在Segue Class字段里,键入SecondCustomSegue的值,让app知道在执行新的自定义转场的时候要用到的类。
第二个自定义转场已经建立好了,接下来实现咱们想看到的动画转换。
正如我在引言里所说,子类化UIStoryboardSegue类,重载perform方法,你能够自由地实现你想要的任何转换。你能够有简单地动画转换,也能够是复杂的,或者彻底没有动画(可是为何要建立一个自定义转场不该用任何自定义动画效果呢?)。
这个转场咱们将指定另外一种转换到第三个视图控制器的动画。此次,咱们将实现缩小和放大的效果。具体来讲,ViewController视图控制器的视图经过按比例缩小视图来消失,这会致使缩小的效果。另外一方面,第三个视图控制器的视图将初始为极小状态,一旦动画执行,它将放大到状态。这会致使放大的效果。教程的概述部分,你能够看到程序运行时的效果。
如今让咱们开始写代码。打开SecondCustomSegue.swift文件,和咱们以前两次作的同样的,经过赋给局部变量建立视图控制器的视图的引用。固然,咱们的实现将发生在perform方法里:
1
2
3
4
5
|
override?func?perform()?{
????
var
?firstVCView?=?sourceViewController.view?as?UIView!
????
var
?thirdVCView?=?destinationViewController.view?as?UIView!
}
|
注意,你能够跳过此步,直接用源和目标视图控制器属性里的视图。然而,我认为那样的话会更清晰,尽管会须要更多几行的代码。
如今,咱们把第三个视图控制器的视图加到窗口上。这是你应该作的事,不然就没有转换过渡到得第二个视图了。
1
2
3
4
5
6
7
|
override?func?perform()?{
????...
????let?window?=?UIApplication.sharedApplication().keyWindow
????window?.insertSubview(thirdVCView,?belowSubview:?firstVCView)
}
|
如今,咱们能够指定第三个视图控制器的视图的初始状态。咱们不动它的结构(frame),咱们修改transform属性,代码以下:
1
2
3
4
5
6
|
override?func?perform()?{
????...
????thirdVCView.transform?=?CGAffineTransformScale(thirdVCView.transform,?0.001,?0.001)
}
|
正如你看到的,咱们按比例缩小它的宽和高。咱们不设置为零,由于这样就彻底没效果了。一个很小的值也是有利于咱们的目的的。
下一步就是执行动画。本例中,咱们应用两个连续的动画,第一个是缩小源视图,而后是放大目标视图。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
override?func?perform()?{
????...
????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)
????????????})
????}
}
|
动画发生的时候,第一个视图变小了,看起来像缩小的效果。在completion handler当中,咱们将对目标视图控制器反转上面的动画。最后,动画结束后,咱们把第一个视图恢复到正常状态,而后展现第三个视图控制器。
这是整个方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
override?func?perform()?{
????
var
?firstVCView?=?sourceViewController.view?as?UIView!
????
var
?thirdVCView?=?destinationViewController.view?as?UIView!
????let?window?=?UIApplication.sharedApplication().keyWindow
????window?.insertSubview(thirdVCView,?belowSubview:?firstVCView)
????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)
????????????})
????}
}
|
自定义转换准备好了。可是咱们还没结束,由于咱们必须执行转场。若是你记得,在ViewController场景里有个叫“Tap Me!”的button。这是咱们用来启动转场的。
首先,打开ViewController.swift文件,添加如下的IBAction方法:
1
2
3
|
@IBAction?func?showThirdViewController(sender:?AnyObject)?{
????self.performSegueWithIdentifier(
"idSecondSegue"
,?sender:?self)
}
|
如你预期的,咱们只是简单地执行转场。注意,转场的标示符的值必须匹配咱们在Interface Builder里设置的值。
最后,咱们必须把上面的方法连到按钮上。打开Main.storyboard.swift文件,在文档大纲里的ViewController场景,点在按钮对象上,而后按住Ctrl和鼠标,拖到ViewController对象上:
出现了一个有不少选项的弹出窗口。里面有个Sent Events,这个下面展现了IBAction方法。选择它,联系就成功完成了。
若是你想限制你能够测试这个app。点在最初的视图控制器的按钮上,观察第三个视图控制器是怎么呈现的。
在演示app里咱们实现的第一个解除转场,我说过你应该作的第一件事是IBAction方法的定义,这个方法在转场执行的时候被调用。这里,咱们实现这样一个方法,叫returnFromSegueActions(sender:),一旦处理好后咱们也将在新的解除转场中用到它。
上面意味着咱们能够去作下一个步骤,在Interface Builder中建立解除转场。打开Main.storyboard文件,在ThirdViewController场景中,选择ThirdViewController对象,按住Ctrl拖动到Exit对象上。
在弹出框里点击操做方法的名字,解除转场就被建立了。
接下来,选择那个转场,打开属性监视器,在identifier字段里,设置idSecondSegueUnwind值。
目前解除转场已经准备好了,咱们能够写咱们想要的转换行为的代码。此次,咱们不只仅执行彻底相反的动画到正常的转场。咱们将建立一个有点复杂的动画,第三个视图控制器的视图将被缩小,同时移向屏幕顶端,第一个视图控制器的视图将被放大,从屏幕底部移动到顶端,因此占据了整个可用区域。这个视图最初准备离开屏幕的可视区域。
打开SecondCustomSegueUnwind.swift文件,咱们将最后一次实现perform方法,让咱们从简单的开始:
1
2
3
4
5
6
7
|
override?func?perform()?{
????
var
?firstVCView?=?destinationViewController.view?as?UIView!
????
var
?thirdVCView?=?sourceViewController.view?as?UIView!
????let?screenHeight?=?UIScreen.mainScreen().bounds.size.height
}
|
和日常同样,咱们让两个视图控制器的视图保存为局部变量,同时把屏幕的宽和高存储在一个变量里。接下来,指定firstVCView视图的初始状态。就像我说的,首先定义离屏的位置,这个将按比例缩小:
1
2
3
4
5
6
7
8
9
10
11
|
override?func?perform()?{
????...
????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)
}
|
除了设置初始状态,咱们也把视图添加到app的窗口上。
最后,让咱们执行动画。记得要消失的视图按比例缩小,要出现的视图按比例放大,两个视图都往顶部移动。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
override?func?perform()?{
????...
????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)
????}
}
|
这就是自定义解除转场的实现。接下来是小结性的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
override?func?perform()?{
????
var
?firstVCView?=?destinationViewController.view?as?UIView!
????
var
?thirdVCView?=?sourceViewController.view?as?UIView!
????let?screenHeight?=?UIScreen.mainScreen().bounds.size.height
????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)
????}
}
|
再次回到ViewController.swift文件,让咱们看下segueForUnwindingToViewController(toViewController:fromViewController:identifier:) 方法,咱们已经讨论过它了,如今咱们必须检查新的解除转场,因此咱们初始化,而后返回一个子类的转场对象。根据咱们以前说的,咱们将再建立一个用例,检查转场的标示符是否和解除转场的匹配,而后再继续。经过添加以下代码修改方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
override?func?segueForUnwindingToViewController(toViewController:?UIViewController,?fromViewController:?UIViewController,?identifier:?String?)?->?UIStoryboardSegue?{
????
if
?let?id?=?identifier{
????????...
????????
else
?
if
?id?==?
"idSecondSegueUnwind"
?{
????????????let?unwindSegue?=?SecondCustomSegueUnwind(identifier:?id,
????????????????source:?fromViewController,
????????????????destination:?toViewController,
????????????????performHandler:?{?()?->?Void?
in
????????????})
????????????
return
?unwindSegue
????????}????????
????}
????...
}
|
这没有什么新的或者难的地方。如今新的解除转场能够彻底执行了。然而,咱们必须先启动,经过在第三个视图控制器里添加新的向上滑动的手势来管理它。因此,到ThirdViewController.swift文件,在viewDidLoad文件里添加以下代码:
1
2
3
4
5
6
7
|
override?func?viewDidLoad()?{
????...
????
var
?swipeGestureRecognizer:?UISwipeGestureRecognizer?=?UISwipeGestureRecognizer(target:?self,?action:?
"showFirstViewController"
)
????swipeGestureRecognizer.direction?=?UISwipeGestureRecognizerDirection.Up
????self.view.addGestureRecognizer(swipeGestureRecognizer)
}
|
咱们只剩下去实现showFirstViewController方法,和其它相似方法同样简单:
1
2
3
|
func?showFirstViewController()?{
????self.performSegueWithIdentifier(
"idSecondSegueUnwind"
,?sender:?self)
}
|
如今,运行这个差很少彻底实现的应用以前,为何不在第三个视图控制器消失的时候显示一条消息呢?
固然能够。不过,和以前的方法不一样,咱们在IBAction这个方法returnFromSegueActions(sender:)里传递数据。咱们已经给它添加了一些简单的代码,咱们也说了这个方法是随着解除转场用来执行各类动做的。
打开ViewController.swift文件,找到上述方法,这里,添加如下的else代码:
1
2
3
4
5
6
7
|
@IBAction?func?returnFromSegueActions(sender:?UIStoryboardSegue){
????...
????
else
{
????????self.lblMessage.text?=?
"Welcome?back!"
????}
}
|
上面添加的消息将每次显示到第一个视图控制器上,第二个解除转场被执行。
如今咱们的演示app完成了。你能够去测试了,固然,若是你想更熟悉自定义转场你能够随意修改任一部分。下面再次展现了app运行的动画。
若是你快速回顾下咱们在教程所作的,你确定会得出这样的结论:使用自定义转场并不难。在视图控制器间应用自定义动画转换,能够有很好的用户体验,也使得你的app和其余的不同凡响。若是自定义转场对你来讲是新的东西,去学习使用他们。我强烈建议你这么作。咱们实现的演示app能够指导你以正确的方式实现自定义转场和解除转场。咱们建立和应用的动画并不复杂,但足够你理解全部的知识点。发挥你的想象力,创造更炫的效果。但愿你能喜欢这篇文章,也但愿它能派上用场。一如既往的,咱们等待你的想法。
供您参考,您能够在这里下载项目。(本文为CocoaChina组织翻译,本译文权利归译者全部,未经容许禁止转载。)