《iOS应用开发》——1.3节到处看看-阿里云开发者社区

开发者社区> 开发与运维> 正文
登录阅读全文

《iOS应用开发》——1.3节到处看看

简介:

本节书摘来自异步社区《iOS应用开发》一书中的第1章,第1.3节到处看看,作者【美】Richard Warren,更多章节内容可以访问云栖社区“异步社区”公众号查看

1.3 到处看看
iOS应用开发
实用工具程序应用应该能用单个单屏显示所有信息,再使用一个背面视图修改偏好设置。iPhone的天气应用就是一个代表性的例子。主屏幕只简单地显示下周的天气预报(即使你可以在不同的城市页面间切换)。背面视图允许你编辑城市列表,还可以在华氏度和摄氏度之间切换。

实用工具应用程序模板创建了这种类型应用的最基本的框架。就模板自身来说,它已经是一个功能完整的应用,可以直接构建和运行。当然,它没有干任何有意思的事情,这正是我们将要加进去的。现在,就让我们看看可以免费获得什么。

1.3.1 运行应用程序
首先,我们需要让Xcode使用模拟器。在Xcode工具栏中点击Scheme按钮并且选择iPhone 5.0 Simulator(见图1.5),接着点击Run按钮,Xcode就会构建应用并且在模拟器中启动。


062efa000488b3778a1124d2eb12a1e55f8cfffa

正如你所看到的,这个应用有一个灰色的主视图,界面右下角有一个信息按钮(见图1.6)。如果你触摸信息按钮,页面就会翻转过来,显示标题栏和蓝色的Done按钮(见图1.7)。触摸Done按钮,就会翻转回主视图。


132300c896a53234d8122941a8e61d996cd6495c

1.3.2 查看文件
现在来看一看模板为我们生成的文件。导航区默认应该会显示,如果没有显示,确认一下导航区视图按钮是否被点中(见图1.8)。

导航区将会在Xcode的主窗口左边显示,它的顶部是导航工具条,底部是筛选工具条(见图1.9)。


931b5644767a29a1133339b1a2de35aea279aa77

我们将使用导航器来筛选和组织大量的信息,包括文件、类、编译错误、调试信息、断点以及日志。你可以通过导航工具条中的图标来改变导航的类型。默认是Project导航,如果不是,请选中看似文件夹形状的那个图标(导航工具条最左边的那个图标)。

Project导航显示了几个分组。Hello World分组包括所有Objective-C类的头文件(.h)以及源文件(.m)。这些文件是我们真正写代码的地方。还包含MainStoryBoard.storyboard资源文件,它定义了应用程序的不同场景以及它们之间的segue。

Supporting Files子分组包含了大量的次要文件。初始状态包含应用程序的Hello World-Info.plist、InfoPlist.strings、main.m以及Prefix.pch等文件。

Hello World-Info.plist文件包含一些用于配置应用的关键字-值对。InfoPlist.strings文件包含了Hello World-Info.plist值的本地化版本。虽然该文件初始状态下是空的,但是在本地化你的应用程序时该文件将会非常有用。main.m文件包含了main()函数,是应用程序启动时最先执行的函数。Prefix.pch文件是应用程序预编译头文件。

为了让大型的项目编译地更快,Xcode允许我们创建一个预编译头,使用#import、#include以及#define等指令包含头文件或者定义宏,这些代码会被大部分代码公用。例如,在一个典型的iOS项目中,大部分类需要访问UIKit和Foundation框架。通过在我们的预编译头中放入这些框架的#import语句,Xcode就知道如何预处理这些文件并且在我们所有的源文件中包含它。这避免了编译器在每次构建应用程序时都重复编译常用的文件。

此刻在这里说,大家可能不会有共鸣,没有关系。本项目中我们不会接触到Prefix.pch文件。如果大家想要了解关于这个文件的更多信息,请阅读苹果公司的参考文献:Xcode Build System Guidelines、Reducing Build Times和Using a Precompiled Prefix Header。

最后,Frameworks分组包含了本项目中可能会使用的任何iOS框架,而Products分组包含了最终果实——也就是我们编译完成的应用。同样,在本项目中我们也不会接触以上这两个分组。

注意:
:和Xcode的工作空间类似,导航区中的分组也只是一个虚拟的分组,它们仅仅在Xcode中才存在。这些分组和实际的资源是如何存储以及存储在何处是没有任何联系的,甚至跟它们包含什么类型的文件也没有联系。你可以自由放置任何资源到任何分组中,或者,如果必要的话可以删除任意分组和子分组。

1.3.3 项目概览
当我刚开始写Cocoa应用程序的时候,我没有真正懂得应用程序的不同部分是如何装配在一起的。当然,我能理解程序的单个模块。我建立一个视图以及视图控制器,它们一起来管理用户界面。然而,很多其他对象在运行时奇迹般地出现了。我完全不知道它们是从哪儿来的。

我再次向你保证,这绝对不是超自然的现象,我已经确认过了。即便这样,花几分钟理一理所有的连接也是非常值得的,看看发生了什么。不用担心记不住所有的细节。而是要把注意力集中在我们如何跟踪一个对象到另一个对象之间的连接。这样的话,以后你就可以独立跟踪自己的应用了。

让我们就从应用程序启动的地方开始。点击导航区中的main.m(如果这个文件没有显示,需要你展开Supporting Files分组)。这个文件实际上非常短,它导入了UIKit框架库以及应用程序委托,并且定义了main()函数。

#import <UIKit/UIKit.h>
#import "HWAppDelegate.h"
int main(int argc, char *argv[])
{ 
     @autoreleasepool {
          return UIApplicationMain(argc, argv, nil,
               NSStringFromClass([HWAppDelegate class]));
     }
}

该函数主要是创建了一个自动释放块。在这个块里,我们启动了Objective-C的应用程序。我们将会在第2章中“内存管理”一节讨论自动释放块。

真正的工作发生在UIApplicationMain()函数中。该函数有4个参数。头两个是负责处理应用程序的命令行参数:argc代表了参数的数量,而argv包含了真正的参数,是一个C风格字符串数组。这两个参数是UNIX系统中的命令行应用程序遗留而来。在iOS系统中,我们的应用通常只有一个参数,也就是应用程序的可执行文件名。我们几乎不会直接使用这两个参数。

接下来的两个参数分别定义应用程序和应用程序委托。每个参数都接收一个相应的类名。如果第3个参数是nil,那么我们就会使用默认的UIApplication类。如果第4个参数是nil,那么我们会从我们的主nib文件中装载UIApplicationDelegate。

然而,在iOS 5.0系统中,应用程序一般会使用storyboards文件,而不是nib文件。这意味着我们需要手动设置应用程序委托的名称。我们要使用自定义的HWAppDelegate类。不妙的是,我们不能直接使用这个类,而是需要调用HWAppDelegate类的class方法来获得我们的应用程序委托对象。然后,我们将这个对象传入NNStringFromClass()函数,这样才会产生与类对应的正确的字符串。

大部分应用都是按照这个形式运行的。我们一般不会从UIApplication派生子类,而是直接使用标准的UIApplication对象,但是提供自定义的应用程序委托。

注意:
你很少会改变这个文件中的内容。事实上,除非你要开发一个特别的东西,不然绝不要改动这个文件。即使你确信你需要做一些改动,还是建议你三思而后行。这几乎总是一个很好的解决方案。
查看STORYBOARD
目前为止一切顺利。main()函数调用UIApplicationMain()函数。这个函数实例化了应用程序类和我们自定义的HWApplication ``Delegate类。接着建立主事件循环并且开始处理事件。如果应用程序的info.plist文件定义了storyboard,那么接下来就会从storyboard加载初始视图控制器和视图。

但是,什么是storyboard呢?

Storyboard允许我们图形化地设计我们的场景并且画出联系这些场景的segue。这是Xcode界面开发的最新技术。

Xcode的先前版本使用Interface Builder来图形化地设计我们的用户界面。Interface Builder将我们的设计保存为nib文件。Xcode可以在我们的应用中包含这些nib文件,在运行时装载它们。

在Xcode 4中,Interface Builder被整合到Xcode中了。这让我们能够同时编辑界面和相应的代码。在iOS 5中,storyboard让我们可以封装一些nib文件,让我们不只是定义一个场景的内部信息,同样也可以定义不同场景之间切换的信息。

正如你将会在本章余下的章节里所见,storyboard包含的内容远远不只是视图和控件。它们还包含了控制器对象,以及不同控件、视图和控制器之间的关联。通过在本章稍后“修改模板”一节拓展“Hello World”应用程序,我们就会获取使用这些关联的经验了。此外,我们将在第3章的结束部分更详细地讨论nib文件,它是storyboard背后的技术。

注意:
最初,Interface Builder将nib存档文件存储为二进制的文件,以.nib为扩展名。Xcode 3.0时,Interface Builder允许将nib文件存储成一个中间XML格式文件,扩展名是.xib。这在源代码管理和其他开发工具之间有了更好的兼容性。应用程序构建时,.xib文件就被编译成为二进制的.nib文件。为了简单起见,这两个格式的文件通常都被大家称作“nib文件”。在Xcode 4.2中,storyboard也以XML文件的方式存储,扩展名是.storyboard。这些文件自动被编译成一个或者多个nib文件。系统运行中需要时就会装载这些nib文件。
那么,UIApplicationMain()函数是如何知道打开哪个storyboard文件的呢?正如我们先前所看到的,主storyboard的名称存储在我们的配置文件中。在Supporting Files分组中点击Hello World-Info.plist。你会看到一系列的关键字-值对。查找关键字Main storyboard的值。它应该被命名为Mainstoryboard。这代表UIApplicationMain()函数将会自动加载MainStoryboard.storyboard文件。

现在让我们打开这个文件。在项目的导航区中点击MainStoryboard. storyboard。这样storyboard文件就会在Xcode的编辑区打开了。你可能想要关闭导航区以便编辑区有尽可能多的空间。你可能还想要在屏幕的右边打开实用工具区(Utilities),那么确保实用工具区的视图按钮被选中(见图1.10)。


0a1bc38b873067471d92042d0b3d29b378b5fc47

我们的项目初始有两个场景。观察一下场景列表,你会发现每个场景至少有两个顶级对象:first responder和场景的视图控制器。先来看看first responder,这从很大程度上来说是一个非常特殊的对象。更确切地说,它根本不是对象,而是代理。系统不会自己创建first responder的实例,而是在运行的时候设置first responder。

first responder代表了应用程序事件响应链中的第一个对象。它可以作为事件响应链中的任何动作的目标。例如,复制动作将会以目前选中的文本为目标,而不管文本是处于什么控件中。我们不想将这个动作硬编码在一个特定的控件中,而是把这个动作传递给当前激活的控件。通过以first responder为目标,我们的消息就会被自动地传送给正确的控件。

说了这么多,事实上,我们在iOS中几乎不会接触first responder。

接着,让我们来看看视图控制器。每个场景都有自己的视图控制器,例如一个UIViewController实例或者是它的子类(更常见的做法)。当我们在第2章讲述“模型-视图-控制器”设计模式时将会更加深入地讨论控制器。目前,只要将控制器视作场景和我们的代码之间的联系吧!

每一个视图控制器都包含一个视图。这个视图又可能包含一系列的子视图和控件(这些控件也可能包含自己的子视图和控件)。我们经常将视图和子视图组成的图(graph)结构称为视图层级结构。Xcode让我们很容易同时查看和操作视图层级结构和视图的实际外观。场景列表显示了层级结构,而Interface Builder显示了它的视觉布局。

我们的场景也可能有别的顶级对象。例如,主视图控制器场景包含一个segue——主视图和背面视图之间的切换。场景还可能把手势识别、视图或者甚至是数据对象作为顶级对象。

所有这些顶级对象(除了segue以外)还会在Interface Builder底部的场景工具条显示。这让在场景界面元素和这些顶级的对象之间拉线建立联系更为轻松,尤其当场景列表中场景越来越多时。另一方面,segue在它所连接的界面之间显示。正如底部的场景工具条,这让它们保持靠近。甚至更重要的是,它图形化地演示了我们应用程序的控制流程,让我们感觉到一切都在掌握之中。

每个storyboard指定了一个场景作为初始场景。当你在从storyboard中实例化这个初始场景时,就会返回这个场景的视图控制器。在我们的这个例子中,初始场景就是在应用程序启动时UIApplicationMain()函数加载的场景。

初始场景在Interface Builder中显示,并且有一个导入箭头指向它。和segues以及其他的联系不一样,这个箭头的另一端没有指向别的场景。默认情况下,导入箭头显示在Interface Builder的左侧边缘。初始场景是第一个场景,当我们往右移动时就有新的场景出现。

通过选择一个不同的视图控制器,并选择它的“is Initial View Controller”属性,这样我们就可以改变初始场景了。同时只有一个场景能启用这个属性。如果你在新的视图控制器里启用它,那么Interface Builder就会自动禁用之前的视图控制器的这个属性。

我们的项目中,主视图控制器场景是我们的初始场景。这正是我们想要的结果。请放大主场景,双击背景就能在storyboard的全景缩略图和实际大小视图中切换。

让我们首先点击主场景中的每一个对象并查看它的类。在Inspector选择器工具条中,请选中Identity inspector(Utilities区顶部从左开始数第三个按钮)。然后点击主视图控制器。不必惊讶,这是一个HWMain ViewController类的实例(见图1.12)。


40b8a47b423b96862d084722b194bd4f73d9dea1

当查看其余的对象时,你会发现我们的视图是一般的UIView。在这个视图中,有一个UI``Button(位于右下角处的信息按钮)。最后,segue在Identity inspector中没有显示任何信息。

继续看背面视图控制器场景,有一个HWFlipsideView Controller。在这个控制器中,有另外一个UIView;然而,在这个视图的顶端有一个UINavigationbar,包含一个UINavigation``Item项。其中就有我们的Done按钮——一个UIBarButtonItem。

现在切换到Connection inspector(Utilities区顶部最右边的图标)。这让我们可以查看和编辑对象之间的联系。然而它可能显得有点复杂,Connection inspector只显示了五种类型的连接:输出口、操作、事件、segue以及关系(relationship)。

前三个是用来定义单个场景中的连接的。输出口是能够存储一个指向对象的指针的变量。它们定义了图层级(graph)中对象之间的联系。另一方面,操作是能够与事件连接的类的方法。当事件发生时,系统就会自动调用相应的操作。控件定义了一组通用的事件,当用户与控件交互时就会触发这些事件。

接下来,segue和关系定义了场景之间的联系。segue类似于过渡。当应用触发了segue,它就会播放指定的动画并过渡到下一个场景。关系和segue很相似,但是它们是用来表现所属关系。容器控制器(类似标签栏视图控制器)将会使用关系来连接它们管理的视图。

查看主视图控制器,我们会发现它包含一个视图输出口,指向被包含的视图对象。这说明我们可以在控制器代码中引用这个视图对象。我们还有一个指向背面视图控制器的引用输出口。这也意味着背面视图控制器有一个指向主视图控制器的输出口,我们马上就会看到(见图1.13)。在这个场景中只有一个连接。点击info按钮,正如你所看到的,它有一个模态storyboard segue连接着它的perform SegueWithIdentifier:sender:方法到背面视图控制器。只要info按钮被按下,系统就会自动调用这个方法触发segue跳转到背面视图。

现在让我们来查看背面视图控制器(见图1.14)。正如我们早先所看到的,界面中有一个委托输出口指回主视图控制器。这让背面视图控制器可以调用主视图控制器中的方法。更具体地说,由于主视图控制器是背面视图控制器的委托,背面视图将会期望主视图实现一些特定的方法,以便修改或者控制背面视图的行为。背面视图控制器还有另外一个与它的视图连接的输出口。正如你的猜测,这是一个通用模式。所有的视图控制器都有一个与其视图连接的输出口。

背面视图控制器还有两个输入的连接。一个是之前我们看到的info按钮的segue。另外一个表示由Done按钮传入的行为。我们马上就会看到。

在这个场景中另外只剩两个连接。导航项通过leftBarButtonItem输出口引用Done按钮。它把工具条正确地放置在导航工具条里。另外,我们可以看到Done按钮的操作和背面视图控制器中的done: 方法相关联。该方法在按下Done按钮时被调用。

最后,选择Attributes inspector(右侧第三个按钮),并且选择segue。这样就显示了segue的详细信息(见图1.15)。Identifier域让我们在代码识别这些segue。Style弹出菜单可以定义新视图显示的方式,而Transition弹出菜单可以定义从一个视图跳转到另一个视图的过渡动画。在我们的例子中,把segue命名为“showAlternate”。我们把新场景显示成模态视图,过渡动画使用水平翻转,即水平翻转旧视图,显示出背面的新视图。


3dfe852ecb1435c61ebe7c59012f59338a7fb7ea

在大多数图形化用户界面中,用户应该先关闭模态视图(通常可以点击“确定”或“取消”按钮关闭视图),才能进行其他的操作。这将强制用户将注意力放在模态视图中的内容上,他们必须在继续下一步之前处理完其中的内容。桌面应用程序中,我们基本上都会使用模态视图来显示诸如保存或者打印对话框。在iPhone中,应用程序的模态视图占据了整个屏幕(虽然iPad还有一些额外的选项)。

那么,这都意味着什么呢?好吧,我们来跟踪一下应用中的对象是如何生成的。UIApplicaitonMain()生成了UIApplication和HWAppDelegate类的实例。

接着,UIApplicaitonMain()在应用的info.plist文件中寻找主storyboard。然后它加载了storyboard的初始场景,在应用窗口中显示主视图控制器的视图。然后,我们的应用就准备好可以接收用户的输入了。

如果用户触摸了info按钮,我们的segue就启动了,它将加载背面视图控制器并且以模态视图的方式显示。当用户触摸Done按钮时,系统就调用背面视图控制器的done:方法。我们知道这样就关闭了背面视图,但是我们还不明白它是怎么运作的。为了弄明白这点,我们就不得不查看应用程序的代码了。

查看代码
虽然storyboard让我们对应用程序有了一个很好的概览,但是要真正理解应用程序是如何运行的,我们还需要深入研究一下代码。如果目前为止你还没有完全看懂,不用担心,我们将在第2章详细讲解Objective-C语言。此刻,只需要对应用程序中各组成部分在哪儿以及它们的作用有个直观感受即可。

让我们从HWAppDelegate.h开始,它声明了HWAppDelegate 类的接口。这里不会有任何令你特别惊奇的事情。HWAppDelegate实现了UIApplicaitonDelegate协议,让它能担当应用程序委托的职责。

委托代表另一个对象的行为或者协同另一个对象运行。委托通常用于协助主委托对象。主对象将调用委托的预定义方法来响应特定事件。委托使用这些方法来监控和控制委托对象。这让我们可以不必修改对象本身,而改变委托对象的行为。

我们还声明了window属性。当应用在加载storyboard时,它会实例化一个窗口对象并且将它赋值给这个窗口属性。通常情况下,iOS应用只会有一个全屏的窗口,这个窗口作为视图层级的根。同时它还创建了一个空间,在这个空间内可以显示其他视图并将事件分发到适当的视图和子视图。

#import <UIKit/UIKit.h>
@interface HWAppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

UIApplicationDelegate协议定义了许多可选的方法,我们可以通过实现这些方法来改变应用程序的行为。这些方法中包含了响应应用程序状态改变的,以及本地、远程和系统通知的方法。你可以在开发文档中找到这些方法的完整清单。

在编辑器中点击UIApplicationDelegate,这将会在Quick Help inspector(实用程序区的上半部分)中显示这个协议的简要描述。这个技巧可以帮助我们快速获得任何一个类、方法或者函数的描述。另外,Quick Help inspector包含了额外资源的一些超级链接。点击协议名称的超级链接,Xcode会从开发者文档中打开完整的描述。向下滚动到Tasks部分就可以查看完整的方法列表。

打开HWAppDelegate.m文件,你就会看到我们定义了一些方法,但是它们并没有做任何事情。目前,它们只是方法存根——保留位置,以便在我们需要的时候填入详细的内容。

#import "HWAppDelegate.h"
@implementation HWAppDelegate
@synthesize window = _window;
- (BOOL)application:(UIApplication *)application

didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{ 
     // 应用启动后,重写此处,以便用户自定义操作
     return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application

{
     /*
当应用程序即将从活动状态切换为非活动状态时调用该方法。临时中断(例如来电或者短信息)或者用户退出应用程序时,就会开始切换到后台状态。

使用这个方法暂停运行的任务、停止计时器、降低OpenGL ES帧率。游戏应用程序应该使用该方法来暂停游戏。

     */
}
- (void)applicationDidEnterBackground:(UIApplication *)application

{
     /*
使用这个方法释放共享的资源、保存用户数据、禁用计时器,并且备份足够的应用程序状态信息,以应对应用程序被终止后,还可以还原应用程序到当前的状态。

如果应用程序支持后台运行,当用户退出时,就会调用该方法而不是application`` ``WillTerminate方法。

     */
}
- (void)applicationWillEnterForeground:(UIApplication *)application

{
     /*
应用程序从后台状态切换到非活动状态调用该方法,你可以在该方法中还原很多进入后台时所做的操作。

     */
}
- (void)applicationDidBecomeActive:(UIApplication *)application

{
     /*
当应用在非活动状态时,重启我们暂停(或者还没启动)的任务。如果应用程序之前是在后台运行的,就可以视需要刷新用户界面。

     */
}
- (void)applicationWillTerminate:(UIApplication *)application{

     /*
当应用程序即将终止时调用。

如果合适的话可以保存数据。

参见applicationDidEnterBackground:。

     */
}
@end

这些方法响应了应用程序状态的变化。当应用程序首次启动时、变为活动时或者非活动时、进入或者退出后台运行或者准备终止时,就会调用以上的方法。我们将会在第3章侧边栏中的“每个应用都会执行的任务”中详细地讨论。此刻,请阅读这些方法存根中的注释。这些注释应该很好地概述了它们的预期用途。

下面,让我们查看HWMainViewController.h文件。这比应用程序委托接口声明还要简单。我们将该类定义为UIViewController的子类。它还采用了一个自定义的协议,即HWFlipsideViewController Delegate。就是这样!

#import "HWFlipsideViewController.h"
@interface HWMainViewController : UIViewController
<HWFlipsideViewControllerDelegate>
@end

选择HWMainViewController.m,查看类的实现。和应用程序委托很像,UIViewController类有一些我们能够重载用来监控视图的方法。具体来说,这包括任何方法名称中存在will或者did的方法。w``ill方法在事件发生之前会被调用。did方法则在事件发生之后会被调用。

在开发者文档中寻找UIViewController,查看整个清单(点 击打开Quick Help inspector,然后点击名称超级链接打开完整的描述)。当你浏览完这个文档,就会发现它包含了许多这样的方法存根。大多数默认的实现是不会做任何操作的;它们仅仅只会调用父类的实现。

只有三个方法含有功能代码:shouldAutorotateTOInterface Orientation:flipsideViewControllerDidFinish:和prepare ForSegue:sender:。

- (BOOL)shouldAutorotateToInterfaceOrientation:

     (UIInterfaceOrientation)interfaceOrientation
{ 
     // 对支持的朝向返回YES
     return (interfaceOrientation !=
          UIInterfaceOrientationPortraitUpsideDown);
}
#pragma mark - Flipside View
- (void)flipsideViewControllerDidFinish:

      (HWFlipsideViewController *)controller
{
      [self dismissModalViewControllerAnimated:YES];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

{
     if ([[segue identifier] isEqualToString:@"showAlternate"]) {
          [[segue destinationViewController] setDelegate:self];
     }
}

让我们依次查看这些方法。ShouldAutorotateToInterface Orientation: 方法是当用户将设备旋转到一个新的方向时会被调用。如果它返回YES,视图将会自动旋转到对应的方向,否则,视图还是会保留当前的方向。当前的实现允许我们的视图旋转到任何一个方向,除了上下颠倒的方向(横屏向左、横屏向右和正向都可以)。

flipsideViewControllerDidFinish: 方法是背面视图控制器的委托方法。你可以从名称来猜想一下,当背面视图控制器完成时就会调用这个方法。我们目前的实现仅仅是关闭当前的模态视图。还记得当我们查看segue的时候,我们看到背面视图控制器是作为一个模态视图显示的。因此,该方法仅仅是关闭背面视图,用同样的过渡动画(水平翻转)来回到主视图。

最后,prepareForSegue:sender: 这个方法会在当前场景任何segue被触发时被调用。虽然这里的实现看起来有一点复杂,但是它仅仅是检查以确保segue的标识符和期望值showAlternate匹配。如果匹配,我们就将主视图控制器指定为目标委托。

在prepareForSegue:sender: 方法中进行任何操作之前校验一下segue的标识符总是一个不错的主意。目前来说,它不是完全必要的。我们只有一个segue,因此我们知道标识符总能匹配上。然而,校验一下标识符对我们未来的代码是有好处的。当我们加入一个新的segue时,就不会突然遇到奇怪的错误了。

另外,如果你记得的话,标识符为showAlternate的segue将主视图控制器和背面视图控制器联系在一起。这意味着目标视图控制器将会是HWFlipsideViewController。

因此,当用户按下info按钮,就触发了segue。系统实例化HWFlipside ViewController的一个副本,接着调用HWMainViewController中的prepareForSegue:sender: 方法。这里,HWMainViewController将自己指定为HWFlipsideViewController的委托。然后开始执行视图切换,视图水平地翻转,显示出背面视图。当背面视图完成时(大概等待一分钟左右),背面视图控制器就会在它的委托上HWMainView`` Controller调用filpsideViewControllerDidFinish:方法。HWMainViewController然后就会关闭背面视图控制器,背面视图就会翻转过来,重新显示主视图。

理解在场景之间首选采用segue来传递数据这种方式是非常重要的。当从原始视图控制器传递数据到目标视图控制器时,我们重写了原始视图控制器的prepareForSegue:sender``:方法并且同时传 递了数据。要将数据传递回来的话,我们将原始视图作为目标视图 的委托并调用恰当的委托方法。当然,这些方法应该在委托协议中声明。下面,我们就来讲解HWFlipsdieViewControllerDelegate协议。

打开HWFlipsideViewController.h文件。这里,我们从声明HWFlipsideViewControllerDelegate协议开始讲起。这个协议只有一个方法,我们之前已经看见过,是flipsideViewController DidFinish:。接着,我们声明HWFlipsideViewController类本身。它也非常简单,只包含两项内容:委托输出口属性以及done:操作。

两者我们之前都见过了。我们刚才讨论过HWMainView Controller委托属性,并且在之前研究storyboard时讨论过done:操作。它连接到导航工具条里的Done按钮。点击该按钮就会触发done:方法。

#import <UI`Kit/UIKit.h>`
`@class HWFlipsideViewController;`
`@protocol HWFlipsideViewControllerDelegate`
`- (void)flipsideViewControllerDidFinish:`
`    `` (HWFlipsideViewController *)controller;`` `
`@end`
`@interface HWFlipsideViewController : UIViewController`
`@property (weak, nonatomic) IBOutlet id`
`<HWFlipsideViewControllerDelegate>`
`    ``delegate;`
`(IBAction)done:(id)sender;`
`@end`

注意:
细心的读者们也许记得在storyboard中delegate输出口也连接了HWMainViewController。这说明它被连接了两次,一次是在代码中,另一次是在Interface Builder中。结果,Interface Builder中的连接实际上不起任何作用。删除它也不会影响应用程序,但是你将不能再次建立它的连接了。然而,需要指出的是 如果注释掉prepareForSegue:sender: 方法,则破坏了连接,并且HWFlipsideViewController的委托属性永远不会被设置。当按下info按钮时仍然会切换到背面视图,但是按下Done按钮时就再也跳转不回去了。
现在打开HWFlipsideViewController.m。和HWMainView Controller.m很相似,这个文件包含很多方法存根。不过,在底部有我们done:操作的实现。

- (IBAction)done:(id)sender

{
     [self.delegate flipsideViewControllerDidFinish:self];
}

该方法仅仅是调用了委托的flipsideViewControllerDidFinish:方法,该方法让我们了解了应用的整个工作流程。当用户按下Done按钮时,事件就触发了done: 操作。然后就调用了HWMainViewController中的flipsideViewControllerDidFinish:方法,这个程序关闭了背面视图控制器场景。现在一切都联系在一起了。


e389e96816917b06bb28690048fa0103356a85d0

再次强调,这里并没有什么超自然的现象发生。如果你在一个新项目中感到迷失了,从主storyboard文件开始(在方案的info.plist文件中定义)并且跟踪下去。你应该能够跟踪从UIApplication对象到项目中的其他对象的一系列连接。有些连接有可能在代码中定义,还有一些有可能在storyboard中定义,只要稍有些耐心,你就会理清所有的连接。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享: