关于如何创建第一个 iOS 应用
本篇“第一个 iOS 应用”教程将向你介绍 iOS 应用开发中的“三个T”:
- Tools(工具)如何利用 Xcode 创建和管理工程。
- Technologies(技术)如何创建能够响应用户输入的应用。
- Techniques(技巧)如何充分利用 iOS 应用开发的基础设计模式。
当你完成本教程的所有步骤之后,你的应用看起来会和下边图中的差不多:
从图中可以看到,你创建的应用界面上有三个主要的元素:
- 一个文本框(用户把文字输入到这里)
- 一个标签(这里可以用来显示信息)
- 一个按钮(它能够让应用在标签处显示信息)
当你完成开发并运行应用时,点按文本框就会呼出系统提供的键盘。使用键盘输入你的名字,关掉键盘(点按 Done 键),再点按 Hello 按钮就会看到“Hello, 你的名字 !”出现在位于文本框与按钮之间的标签上。
要充分用好这份教程,如果已有一些基本的电脑编程概念并了解面向对象编程、Objective-C 语言是再好不过的了。如果你从未用过 Objective-C 也不要担心,文中的代码并不难懂。而且当你读完全部《iOS 应用开发入门指南》路线图之后,你会更好地理解代码。
注意:你可以通过这份教程开始学习整个 iOS 平台的应用开发,即便你只打算为 iPad 进行开发。虽然教程中以 iPhone 界面举例,但是所使用的工具和技术跟 iPad 应用开发是完全一样的。
要跟随本教程创建 iOS 应用,请先下载并安装 Xcode 4.3 或更新版本。Xcode 是苹果公司的集成式开发环境(IDE),可以同时用来开发 iOS 和 Mac OS X 应用程序。当你在 Mac 电脑上安装 Xcode 之后,同时也会安装 iOS SDK,其中包含了 iOS 平台开发所需的接口等。
创建并测试新工程
开发新应用,从创建一个 Xcode 新工程开始。
如何创建新工程…
- 打开 Xcode(默认位置在 /应用程序 目录下)。
如果你从未使用 Xcode 创建或打开过工程,你将看到和图中类似的 Xcode 欢迎界面:如果你曾经创建或打开过 Xcode 工程,你可能就会看到工程窗口,而不是欢迎界面。- 在 Xcode 欢迎界面中,点选“Create a new Xcode project”(或点选 File > New > Project)。
Xcode 将开启一个新窗口并显示对话框,让你选择一个模板。Xcode 内置了几套应用模板,可以方便开发常规 iOS 应用。例如,Tabbed 模板能够创建一个和 iTunes 类似的应用,而 Master-Detail 模板能够创建和 Mail 相似的应用。- 在对话框左侧的 iOS 部分选择 Application。
- 在对话框右侧的主要区域选择 Single View Application 然后点按 Next。
接下来的对话框会让你输入应用的名称,以及关于工程的一些额外信息。- 填写 Product Name(产品名称)、Company Identifier(公司标识)以及 Class Prefix(类前缀)。
你可以按下面的内容填写:
- Product Name:HelloWorld
- Company Identifier:如果你有公司,就填写公司名称。如果没有,就填写 edu.self。
- Class Prefix:HelloWorld
注意:Xcode 会使用你输入的产品名称为工程以及应用程序命名。Xcode 会使用你提供的类前缀名称来命名它为你创建的类。例如,Xcode 会自动创建一个应用委托类并将其命名为 HelloWorldAppDelegate。如果你填写了其他的类前缀,那么应用委托就会被命名为 你的前缀名称AppDelegate。(之后你会了解更多关于应用委托的内容。)
为了讲解的方便,本教程假定你的产品名为 HelloWorld 并且你使用 HelloWorld 作为类前缀。
- 在 Device Family 弹出菜单中,确认选中了 iPhone。
- 请确认 Use Storyboards 和 Use Automatic Reference Counting 选项是选中的,并且 Include Unit Tests 选项不被选中。
- 点按 Next.
会出现另一个对话框,可以选择在哪里保存工程文件。- 选择一个保存位置(不要选中 Source Control 选项),然后点按 Create 完成创建。
Xcode 便会打开一个新工程窗口(我们叫它工作区窗口),看上去应该和下图类似:花几分钟时间来熟悉一下 Xcode 为你打开的工作区窗口吧。在接下来的教程里,你会经常用到图中不同的区域和按钮。
如果你的工作区窗口里已经打开了实用工具区域(如上图的黄色区域),你可以暂时将其关闭,稍后才会用到它。在 View 按钮中最右边的就是实用工具区域的开关。当实用工具区域是显示状态时,该按钮应该是按下去的:
如果需要,则可以点按 View 按钮中的最右边一个来关闭实用工具区域。
尽管迄今为止你连一行代码都没有写,其实你已经可以编译此应用并在 iOS 模拟器中运行(已包含在 Xcode 中)。人如其名,iOS 模拟器能够让你直观感受你的应用在 iOS 设备上运行起来是什么样子。
如何在 iOS 模拟器中运行你的应用…
- 首先确认一下 Xcode 工具条里的 Scheme 菜单选中了 HelloWorld > iPhone 5.0 Simulator。
如果该菜单显示的不是这一项,那么点开它并选择 iPhone 5.0 Simulator。- 点按 Xcode 工具条中的 Run 按钮(或点选 Product > Run)。
Xcode 会在工具条中央的活动查看器里实时显示构建过程。当 Xcode 完成构建过程之后,模拟器就会自动运行(可能要等几秒模拟器才会出现在工作区窗口上方)。因为你已经选择过 iPhone(而不是 iPad),所以模拟器会显示一个和 iPhone 一样的界面。在虚拟的 iPhone 屏幕上,模拟器会自动运行你的应用,看上去应该是这样的:
现在,你的应用里什么也没有:它仅仅显示一个白屏。要了解这个白屏来自哪里的话,你需要学习代码里的对象,并且学习它们如何协同工作启动这个应用。现在,请退出模拟器(点选 iOS Simulator > Quit iOS Simulator。请注意不要退出 Xcode)。
在你运行应用到时候,Xcode 可能会展开工作区窗口底部的 Debug(调试)区域。在本教程里你不会用到这个区域,你可以将其关闭。
如何关闭调试区域…
- 点按工具条里 View 按钮中的调试按钮。
View 按钮中的调试按钮是中间一枚,图标是这样的:弄明白一个应用是如何运行起来的
由于你的工程是基于 Xcode 的一个模板建立的,在运行应用的时候,大部分基础应用环境已经自动设置好了。例如,Xcode 会创建一个应用程序对象,它能够建立一个运行循环(Run Loop,即一种事件处理循环。它能够输入资源并让接收到的事件传送到你的应用里去)。这里的大部分工作是由UIApplicationMain 函数完成的,这个函数是由 UIKit 框架提供的,在工程 main.m 源文件里已经自动被调用。
注意:UIKit 框架提供了一个应用所需的从构建到管理用户界面的所有类。UIKit 框架只是 Cocoa Touch 面向对象框架中的一种,它专为 iOS 应用环境服务。
如何查看 main.m 文件的源代码…
- 确保工程导航栏是打开的。
工程导航栏显示了工程里的所有文件。如果工程导航栏没有打开,点按导航栏选择列最左边的的按钮:- 在工程导航栏中打开 Supporting Files 文件夹,点按左侧的三角形按钮即可。
- 点选 main.m。
Xcode 会在此窗口的主要编辑器区域显示文件里的代码,看上去应该和图中类似:源文件 main.m 里面的 main 函数会调用 UIApplicationMain 函数,并且是被包含在一个自动释放池里的:
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([HelloWorldAppDelegate class]));
}第一行的 @autoreleasepool 语句可以支持自动引用计数系统(ARC)。ARC 可以自动管理应用中的对象活动周期,确保在程序需要它们的时候一直存在,并在不需要的时候释放这些对象。
调用 UIApplicationMain 函数时会创建一个 UIApplication 类的实例和一个应用委托的实例(在本教程中,应用委托就是 HelloWorldAppDelegate,这是 Single View 模板自带的)。应用委托的主要任务就是提供一个窗口,从而让你的应用能够在里面绘制内容。应用委托还能在应用界面显示出来之前执行一些配置任务。(委托机制是一种设计模式,它规定:一个对象要代表另一个对象,或与另一个对象协同进行活动。)
在 iOS 应用中,窗口对象提供了一个可见容器,帮助把事件传送到应用对象中去,并且帮助应用响应设备在方向上的改变。而窗口本身是不可见的。
调用 UIApplicationMain 函数同时还会扫描应用的 Info.plist 文件,这个文件是一个包含应用名称、图标等信息的属性列表文件(即结构化的键/值对列表)。
如何查看属性列表文件…
- 在工程导航栏的 Supporting Files 文件夹里,点选 HelloWorld-Info.plist。
Xcode 会在编辑器区域显示 Info.plist文件的内容,看上去应该和图中类似:在本教程中,你暂时无需了解 Supporting Files 文件夹里的任何文件,所以可以收起它避免影响视线。收起文件夹也是点按 Supporting Files 文件夹左侧的小三角按钮。因为你刚才选择在此工程里使用故事板,于是 Info.plist 文件里同样也会包含应用程序对象需要载入的故事板文件的名称。故事板里包含了定义此应用程序用户界面的对象、过渡(Transition)以及连接(Connection)。
在 HelloWorld 应用中,故事板文件被命名为 MainStoryboard.storyboard(注意,Info.plist 文件只会显示文件名的前半部分)。当应用启动时,MainStoryboard.storyboard 会被加载并且初始视图控制器(Initial View Controller)会通过它创建实例。视图控制器(View Controller)是一个对象,用来管理一片区域中的内容;初始视图控制器就是应用启动时被载入的第一个视图控制器。
HelloWorld 应用只含有一个 View Controller(说白了就是 HelloWorldViewController)。现在,HelloWorldViewController 管理着一片区域的内容,它就是由一个视图(view)提供的。视图也是一种对象,它能够在屏幕上一个矩形区域内进行绘制,并且处理用户点、触时产生的事件。一个视图中可以包含其他视图,被包含的视图就叫做子视图。当你向一个视图内添加子视图时,最外层的视图叫做父视图,而它的子视图按理就被称为孩子视图。父视图和一系列孩子视图(如果继续包含下去,孩子视图还会有自己的孩子视图)共同构成视图层级。一个视图控制器管理一个层级的视图。
注意:HelloWorld 应用对象被定义为一种叫做模型-视图-控制器(MVC)的设计模式,视图及视图控制器就是这三个角色中的两个。第三个角色就是模型对象。在 MVC 里,模型对象代表数据(例如日历应用里的一个代办事项,或某画图应用里的一个形状),视图对象知道如何显示模型对象所提供的数据,而控制器对象则是模型和视图中间的连接部分。在 HelloWorld 应用里,模型对象是一个用来保存用户输入的姓名的字符串。目前你不用太深入地了解 MVC,但是如果能够在设计过程中时刻想象你应用中的对象是如何扮演这三个角色的也很棒。
在接下来的步骤中,你需要在 HelloWorldViewController 管理的视图中添加三个子视图来形成视图层级;这三个子视图分别是一个文本框、一个标签和一个按钮。
在故事板中你可以看到视图控制器和视图的可视化表现形式。
如何查看故事板…
- 在工程导航栏中点选 MainStoryboard.storyboard。
Xcode 会在编辑器区域打开故事板。(而故事板后边的区域,也就是看上去像空白图纸的区域,叫做画布(Canvas))。当你打开默认的故事板时,工作区窗口看起来应该和图中类似:
每个故事板都包含若干 Scene(场景)和 Segue(接续)。场景代表一个视图控制器,接续代表两个场景之间的过渡转换。
由于 Single View 模板只提供一个视图控制器,所以你应用的故事板里就只有一个场景,没有接续。画布上场景左侧的一个箭头是初始场景指示器,表明它指着的场景是应用载入时第一个显示的场景(典型情况下初始场景就相当于初始视图控制器)。
你在画布上看到的这个场景名称是 Hello World View Controller 因为它受 HelloWorldViewController 对象的管理。Hello World View Controller 是由 Xcode 大纲视图里显示的若干个部件构成的。大纲视图就是画布和工程导航栏之间的那一栏。目前,视图控制器包含一下几个部件:
- 一个 first responder 占位符对象(由一个橙色的立方体标示)。 这里的 first responder 是一个动态占位符(注意“动态”两字的含义,说明最先接受事件的对象随着根据用户的操作可能会变),代表应用运行过程中应该最先接收事件的对象。这些事件包括编辑焦点事件(例如点按文本框时呼出键盘),动作事件(例如摇晃设备),以及动作消息(例如用户按下的按钮发出的消息),等等这些。在本教程中你无需对 first responder 做任何修改。
- 一个 HelloWorldViewController 对象(由一个嵌有矩形面板的橙色球体代表)。 当故事板载入一个场景时,它就会创建一个视图控制器类的实例来管理这个场景。
- 一个视图,它被列在视图控制器的下边(要在大纲视图里显示这个视图,你需要点按 Hello World View Controller 旁边的三角形按钮来展开)。 这个视图的白色背景便是你在模拟器中运行应用时看到的白屏。
注意:应用的窗口对象并不在故事板里。
画布中场景下面的部分叫做 Scene Dock(场景坞)。现在场景坞里显示了一些视图控制器的名称(也就是 Hello World View Controller)。在其他时候,场景坞可能包含一些代表 first responder 和视图控制器对象的图标。
本节回顾
在本章节里,你使用 Xcode 创建了一个基于 Single View 模板的工程,并构建和运行了模板定义的默认应用。然后学习了工程中的一些基本要素,比如 main.m 源代码文件,Info.plist 属性列表文件,以及故事板文件,然后学习了关于应用如何启动的原理。你还学到,你应用中的对象是如何分别代表模型-视图-控制器(MVC)设计模式中这三个角色的。
在下一章节里,你将学到关于视图控制器及其视图的更多内容。
正如你之前所学,一个视图控制器负责管理一个场景,以及呈现一个区域里的内容。这个区域中你看到的内容便是由视图控制器中的视图定义的。在这个章节中,你会更加细致地观察到由 HelloWorldViewController 管理的场景,并学会如何如何改变视图的背景颜色。
使用检视器来检视视图控制器
当一个应用启动时,就会加载主故事板文件,并创建一个初始视图控制器的实例。初始视图控制器管理着用户运行应用时看到的第一个场景。由于 Single View 模板只提供一个视图控制器,它也就自然成为初始视图控制器。你可以利用 Xcode 检视器(Inspector)来观察视图控制器的状态等相关信息。
如何打开检视器…
- 在工程导航栏点选 MainStoryboard.storyboard,画布上就会出现它的场景。
- 在大纲视图中,点选 Hello World View Controller(就位于 First Responder 下边)。
你的工作区窗口看上去应该和图中类似:
请注意场景和场景坞上同时显示了一个蓝色方框,而且场景坞中视图控制器对象是被选中的状态。
- 点按位于工具条上的 View 按钮最右边一个按钮,展开右侧的实用工具区域(或者点选 View > Utilities > Show Utilities)。
- 点选实用工具区域中的 Attributes(属性)检视器按钮,打开属性检视器。
检视器按钮是位于实用工具区域顶部检视器选择条中的第四个按钮。
打开属性检视器后,你的工作区窗口看起来应该和图中类似(可能你需要将窗口扩大来查看所有内容):
在属性检视器的 View Controller(视图控制器)部分,你可以看到 Initial Scene(初始场景)选项是被选中的:
注意,如果你取消选择这个选项,那么画布上的初始场景指示器就会消失。在本教程中,请确保 Initial Scene 选项一直是被选中状态。
改变视图的背景颜色
在之前的教程中,你曾学到应用在模拟器中运行时,你看到的白色背景就是由视图提供的。要确保应用能够正常运行,你可以将视图的背景色替换成别的颜色,并查看应用在模拟器中运行时能否显示新颜色。
在改变视图背景颜色之前,请确保故事板在画布上是打开的。(如果需要,请在工程导航栏中点选 MainStoryboard.storyboard 在画布上打开故事板。)
如何设置视图控制器中视图的背景颜色…
- 在大纲视图中,点按 Hello World View Controller 旁边的三角箭头(如果它是收起的状态),然后点选 View(视图)。
Xcode 会高亮显示画布上的视图区域。- 在实用工具区域顶部的检视器选择条中点选 Attributes 按钮,打开属性检视器。
- 在属性检视器中,点按 Background(背景)菜单那里的白色矩形,打开 Colors(颜色)窗口。
菜单中的矩形会显示当前的背景颜色。Background 菜单看起来应该和图中类似:
注意:如果不是点按白色矩形而是 Default 字符,则会弹出颜色选择菜单,此时请之间点选 Other(其他)。
- 在 Colors 窗口中,点选除了白色的任意颜色。
你的工作区窗口(包括 Colors 窗口)看上去应该和图中类似:
需要注意的是,当你选择一个视图时 Xcode 会将其高亮显示,此时画布中的颜色因为叠加可能会和 Colors 窗口中所选的颜色不太一致。
- 选好后关闭 Colors 窗口。
请确保 Xcode 工具条中 Scheme 菜单里仍然显示为 HelloWorld > iPhone 5.0 Simulator。你看到的画面应该和图中类似:
提示:在运行应用之前,你无需手动保存工程。因为当你按下 Run 按钮(或点选 Product > Run)时,Xcode 就已经自动将你所做的修改全部保存了。
在继续阅读后面的教程之前,请把视图的背景色改回白色。
如何把视图的背景色改回来…
- 在属性检视器中,点按 Background 菜单中的箭头打开菜单。
请注意,Background 菜单中的矩形块颜色已经变成了你刚才在 Colors 窗口中选择的颜色。如果你此时点按了矩形块(而不是箭头),那么仍会打开 Colors 窗口。但是我们现在要使用的是视图的原始背景色,所以点开 Background 菜单会更好寻找并直接应用原始颜色,而不是在 Colors 窗口里调配。- 在 Background 菜单中点选 Recently Used Colors(最近使用颜色)中的白色方块。
- 点按 Run 按钮,编译并运行你的应用(当然,所做的修改会被自动保存)。
当你看到应用又能显示出白色的背景时,就可以退出 iOS 模拟器了。
本节回顾
在本章节里,你检视了场景并改变(之后恢复)了视图的背景颜色。
在下一章节中,你将要向视图中添加一个文本框、一个标签和一个按钮,让应用与用户产生互动。
Xcode 提供了一个对象库,方便将对象添加到故事板文件。其中,有些是用户界面元素,属于视图类,比如按钮、文本框等;其他的是更高等级的对象,比如视图控制器、手势识别器等。
Hello World View Controller 场景已经包含了一个视图,现在你只需要再添加一个按钮、一个标签和一个文本框就可以了。然后你需要将这些部件和视图控制器类连接起来,这样它们才能够按你需要的方式行事。
添加用户界面元素
把对象库里的用户界面(UI)元素拖放到画布就可以将它们添加到视图中去了。当 UI 元素被放到视图中后,你可以按需要进行移动或缩放。
如何将 UI 元素添加到视图,并恰当摆放…
- 在工程导航栏选中 MainStoryboard.storyboard,画布上便会显示出 Hello World View Controller 的场景。
- 打开对象库。
对象库位于实用工具区域的底部。如果你没有找到对象库,可以点按库选择条中左起第三个按钮:
- 在对象库的 Objects 菜单中选择 Controls。
Xcode 会在菜单下边显示一个控件(Controls)列表,列表里包括各个控件的名称和外观以及关于其功能的简要说明。
- 把文本框(text field)、圆角按钮(round rect button)和标签一次一个从列表中拖出来,并放置到视图上去。
- 拖动视图中的文本框,让它位于视图的左上角。
在你移动文本框(以及任意 UI 元素)时,会出现一些蓝色的虚线,它们叫做对齐引导线,它们能帮你轻松在视图中将 UI 元素对齐到边界或中心位置。当文本框上边和右边同时出现对齐引导线时请停止拖动,如图所示:
- 在视图中,准备缩放文本框。
通过拖拽 UI 元素上的缩放柄(Resize Handle),即位于元素边缘上的白色小方块,就可以缩放元素的大小。一般情况下,在画布或者大纲视图中点选某个元素时它的缩放柄就会出现。在这里,文本框应该已经被选中,因为你刚刚完成对齐拖放。如果你的文本框看起来和下边的类似,那么就可以进行缩放了。如果不一样,请在画布或者大纲视图中点选它。
- 向右拖动文本框右侧的缩放柄,直到视图右侧出现对齐引导线。
当你看到这样的画面时请停止拖拽:
- 保持文本框为选中状态,打开属性检视器。
- 在文本框属性检视器靠近顶部的位置,在 Placeholder(占位符)框里填写 Your Name。
Placeholder 里的文字可以帮助用户理解文本框里需要填写哪些内容,在应用运行时提示文字以灰色呈现,并且在用户点按文本框准备输入时自动消失。
- 然后在文本框属性检视器里,点按 Alignment(对齐)按钮的中间一个,这样可以让文本框里的文字居中对齐。
当你输入了占位符文字以及修改了对齐选项之后,文本框属性检视器看起来应该和图中类似:
- 在视图中,拖动标签(Label)让它位于文本框的正下方,其左边和文本框的左边对齐。
- 拖动标签的右缩放柄,使其刚好和文本框等宽。
可以看到,标签的缩放柄比文本框的要多,因为标签可以横向缩放也可以纵向缩放(但是文本框只支持横向缩放)。我们不需要改变标签的高度,因此请不要拖动标签四个角上的缩放柄,而要拖动右侧中间的那一个。
- 在标签属性检视器中,同样点按 Alignment 按钮的中间一个,让标签里显示的文字居中对齐。
- 将按钮拖动到视图底部附近,并且放在视图正中央。
- 在画布上,连按按钮并输入文字 Hello。
当你在视图中连按一个按钮时(在输入文本之前),你看到的应该和图中类似:
在添加了文本框、标签和按钮这些 UI 元素并按上述方法修改布局以后,你的工程看起来应该和图中类似:
文本框还有另外几个选项,通过修改这些选项可以让文本框的行为更加符合用户的预期。首先,用户会根据提示输入自己的名字,你可以确保 iOS 建议让每一个单词首字母都大写;其次,你要确保和文本框关联的键盘设置适合于输入姓名(而不是数字什么的),而且要让键盘中显示一个 Done(完成)按钮。
这些修改都基于一个原则:既然你清楚文本框里将要输入什么内容,那么就应该将其外观及动作设置到最适合用户完成相应任务的状态。所有调整和配置都可以在属性(Attributes)检视器中完成。
如何配置文本框…
- 在视图中,选中文本框。
- 在文本框属性检视器中,进行以下几项修改:
- 在 Capitalization(大写)菜单里选择 Words(单词)。
- 确保 Keyboard(键盘)菜单选择的是 Default(默认)。
- 在 Return Key(回车键)菜单里选择 Done(完成)。
- 当你完成这些配置后,文本框属性检视器看起来应该是这个样子:
在 iOS 模拟器中运行你的应用,确保你添加的所有 UI 元素看起来都是你所希望的样子。你在点按 Hello 按钮时,它应该会高亮一下,如果你点按文本框里面的部分,键盘就应该显示出来。目前按钮还没有任何功能,标签也只会显示“Label”,并且键盘一旦被调出就无法收回去了。要添加你需要的功能,就得把 UI 元素和视图控制器进行正确连接。接下来,我们来看如何进行连接。
注意:由于你正在 iOS 模拟器中运行此应用,而不是真实设备中,你激活控件的方式是点按而不是真实的轻敲。
为按钮创建动作
当用户激活某个 UI 元素时,该元素会发送一个消息给能够做出对应的动作的对象(例如“将此联系人添加到用户的联系人列表中”)。这种交互行为是“目标-动作机制(Target-action mechanism)”中的一部分,这是另一个 Cocoa Touch 的设计模式。
在这篇教程里,用户轻敲 Hello 按钮时,你要让按钮给视图控制器(即目标)发送一个“更改欢迎辞”的消息(即动作)。这个消息将修改一个由视图控制器管理的字符串(也就是模型对象)。然后,视图控制器去更新标签里显示的文字,反映出模型对象中这个值的改变。
在 Xcode 中,给某个 UI 元素添加动作并设定相应的动作方法的途径是:按住 Control 键并从画布上的该元素拖动到合适的源文件上(一般而言就是视图控制器的源文件)。故事板会把通过这种方式建立的连接进行存档。之后,当应用运行载入故事板时,这些连接就会被还原出来。
如何给一个按钮添加动作…
- 在工程导航栏里选择 MainStoryboard.storyboard,画布中会显示其场景。
- 在 Xcode 工具条里,点按 Utilities(实用工具)按钮关闭实用工具区域,然后点按辅助编辑器按钮(Assistant Editor)调出辅助编辑器面板。
辅助编辑器按钮位于 Editor(编辑器)按钮中间,图标是这样的:
- 确保辅助编辑器里显示的是视图控制器的头文件(即 HelloWorldViewController.h)。
- 在画布中,按住 Control 并从 Hello 按钮拖动到 HelloWorldViewController.h 的方法声明部分(即 @interface 语句到 @end 语句之间的部分)。
按住 Control 并拖动的时候,你看到的应该和图中类似:
拖动到目标位置并松开 Control 键后,Xcode 将弹出一个配置框,在里面可以配置你刚刚完成的动作连接:
注意:如果按住 Control 键拖动时在 HelloWorldViewController.h 的方法声明区域以外的地方松开,你将会看到其他样子的弹出框甚至什么也不会出现。如果不小心这样,可以点按画布中的视图来取消该弹出框(如果需要的话),然后再次按住 Control 键拖动。
- 在弹出框中,这样配置按钮的动作连接:
- 在 Connection(连接)菜单中,点选 Action(动作)。
- 在 Name(名称)文本框里输入 changeGreeting:(可不要漏掉那个冒号呀)。 接下来的步骤中,你需要实现 changeGreeting: 方法,这样才能真正让它将用户输入的文字“放”到标签上显示出来。
- 确保 Type(类型)文本框里是 id。
id 型数据可以标示任何一种 Cocoa 对象。我们在这里选用 id 是因为无需关心发送消息的对象究竟是什么。- 确保 Event(事件)菜单里选中的是 Touch Up Inside。 之所以选用 Touch Up Inside 事件是因为我们要在用户的手指在按钮范围内抬起时发送消息。
- 确保 Arguments(参数)菜单中选中的是 Sender。
- 当你配置完动作连接之时,弹出框里的内容看上去应该和图中类似:
- 点按弹出框右下角的 Connect(连接)。
Xcode 会为新的 changeGreeting: 方法添加一个存根实现,并且在这个方法的左侧显示一个内部有填充的圆形来标示已经进行过连接:
当你按住 Control 键并从按钮元素拖动到 HelloWorldViewController.h 文件并配置对应的动作时,其实你完成了两件事:
- 你向视图控制器类添加了适当的代码。明确地说,你向 HelloWorldViewController.h 添加了如下动作方法声明:
- (IBAction)changeGreeting:(id)sender;
并让 Xcode 向 HelloWorldViewController.m 文件添加了如下存根实现代码
- (IBAction)changeGreeting:(id)sender {
}注意:IBAction 是一个特殊的关键词,用来告诉 Xcode 把一个方法当成目标-动作连接来看待。IBAction 被定义为 void。
而动作方法中的 sender 参数指向的是动作消息的发送者(在这篇教程里,发送者就是按钮)。
- 你已经在按钮和视图控制器之间建立好连接了。
接下来,需要在视图控制器和剩下的两个 UI 元素之间建立连接,即标签和文本框。
为文本框和标签创建插座变量(Outlet)
插座变量(Outlet)描述的是两个对象之间的连接。当你需要让一个对象(例如视图控制器)与它所包含的一个对象(例如文本框)进行沟通时,你就会把被包含的那个对象称为插座变量。当应用运行起来时,你在 Xcode 里创建的插座变量就被还原出来了,这样在运行时这些对象就可以相互交流了。
在本篇教程中,我们需要让视图控制器获取用户在文本框里输入的文字并将其显示到标签上。为了确保视图控制器能够和这些对象进行沟通,你要在它们之间创建插座变量连接。
要为文本框和标签添加插座变量,步骤和添加按钮动作时的很相似。在开始操作之前,确保画布上仍然可以看到主故事板文件,并且 HelloWorldViewController.h 在辅助编辑器中是打开的。
如何为文本框添加插座变量…
- 按住 Control 键并从视图中的文本框拖动到头文件的方法声明区域。
当你进行 Control 拖动时,看起来应该和图中差不多:
只要按住 Control 拖动到了方法声明的区域里,并在区域里的任何地方松开便可。在本篇教程中,文本框和标签的插座变量声明显示在 Hello 按钮的方法声明上边。
- 完成 Control 拖动并松开时,会出现一个弹出框,请这样配置文本框的连接:
- 确保 Connection(连接)菜单里选中的是 Outlet。
- 在 Name(名称)文本框里输入 textField。
你怎么称呼这个插座变量都行,但插座变量和它所代表的物体在名称上有关联的话,你的代码会更易读。- 确保 Type(类型)文本框里是 UITextField。
将 Type 文本框设置为 UITextField 就确保 Xcode 只会把这个插座变脸和文本框相连接。- 确保 Storage(存储)菜单中选中的是 Weak(弱),这也是默认选项。
- 当你完成配置之后,此时的弹出框看上去应该是这样的:
- 点按弹出框右下角的 Connect(连接)。
为文本框添加插座变量时,你实际上完成了两件事:
- 你向视图控制器添加了适当的代码。明确地说,就是你向 HelloWorldViewController.h 中添加了下列声明:
@property (weak, nonatomic) IBOutlet UITextField *textField;
注意:IBOutlet 是一个特殊关键词,用来告诉 Xcode 把该对象当作插座对象来对待。它实际上未被定义为任何东西,所以在编译时是没有影响的。
同样,你还让 HelloWorldViewController.m 文件的 viewDidUnload 方法里新增了下列语句:
self setTextField:nil;
viewDidUnload 方法是由你选用的 Xcode 模板提供的,并且 UIKit 框架已经帮你实现它了。当某个视图控制器需要卸载它所包含的某个视图时,就会调用 viewDidUnload 方法,因此这个方法正好可以用来将视图的插座变量设为 nil。
- 你已经建立好了从视图控制器到文本框的连接。
在视图控制器和文本框之间建立连接后,用户输入的文字就会被发送给视图控制器。在 changeGreeting: 方法声明的部分,Xcode 会在文本框声明语句的左侧显示一个含有填充的圆形表示已经连接。
现在给标签也添加一个插座变量并配置连接。在视图控制器和标签之间建立连接后,视图控制器就可以用包含用户输入文字的一个字符串来更新标签显示的内容。这一步的步骤和刚才给文本框添加插座变量时是相同的,只不过在配置连接时稍有区别。(确保辅助编辑器中的内容是 HelloWorldViewController.h。)
如何给标签添加插座变量…
- 按住 Control 键并从视图上的标签拖动到辅助编辑器里 HelloWorldViewController.h 的方法声明区域。
- 松开后会出现弹出框,请按如下方式配置标签的连接:
- 确保 Connection(连接)菜单里显示的是 Outlet(插座变量)。
- 在 Name(名称)文本框中填入 label。
- 确保 Type(类型)框里显示的是 UILabel。
- 确保 Storage(存储)菜单里显示的是 Weak(弱)。
- 点按弹出框右下角的 Connect(连接)。
跟着教程走到这一步,你已经创建了三个到视图控制器的连接:
- 一个按钮的动作连接
- 一个文本框的插座变量连接
- 一个标签的插座变量连接
在 Connections(连接)检视器中可以检验这些连接。
如何打开视图控制器的连接检视器…
- 点按 Standard(标准)编辑器按钮来关闭辅助编辑器,切换到标准编辑视图。
标准编辑器按钮是 Editor 按钮中最左边一个,看上去是这个样子:
- 点按 View 按钮中的 Utilities(实用工具)按钮,打开实用工具区域。
- 在大纲视图中点选 Hello World View Controller。
- 在实用工具区域打开连接检视器。
连接检视器按钮在检视器选择条的最右边,看上去是这个样子:
在连接检视器中,Xcode 会显示选中对象的所有连接情况(在这里是视图控制器的所有连接)。在工作区窗口,你应该能够看到图中的画面:
注意,除了你创建的三个连接之外,视图控制器和它的视图之间也有连接。Xcode 默认会在视图控制器和它的视图之间自动做好连接,你可以不必理会。
为文本框的委托建立连接
你的应用里还缺少一个连接:你需要规定某个对象为文本框的委托(Delegate),并在文本框和文本框委托之间建立连接。在本教程中,你将使用视图控制器作为文本框委托。
为什么要给文本框规定委托对象呢?因为当用户轻敲键盘中的 Done 按钮时,文本框要给它的委托发送消息(回忆一下,委托是一种对象,它代表另一个对象执行动作)。在之后的步骤里,你要利用跟这个消息相关联的方法来收起键盘。
确保故事板文件显示在画布上。如果没有,请在工程导航栏中点选 MainStoryboard.storyboard。
如何为文本框设定委托…
- 在视图中,按住 Control 键并从文本框拖动到场景坞里的黄色球体上(黄色球体代表视图控制器对象)。
当你松开时,应该能看到图中的画面:
- 当半透明面板出现时,选择 Outlets(插座变量)部分的 delegate。
测试应用
点按 Run 来测试你的应用。
现在你应该可以看到,在点按 Hello 按钮时它会高亮,点按文本框内部时会出现键盘并且能够输入文字。然而,我们仍然无法收起键盘。在下一章节里,我们将实现这个功能,也就是实现适当的委托方法。现在请退出 iOS 模拟器。
本节回顾
当你在画布中的视图控制器和辅助编辑器中的 HelloWorldViewController.h 文件之间建立了合适的连接以后,你同时也更新了实现文件(即 HelloWorldViewController.m 文件)来支持相应的插座变量和动作。如果想要检视实现文件中发生的变化,在工程导航栏中打开它即可。
在创建连接时,你也可以不使用 Xcode 的自动添加代码功能(即按住 Control 键从画布拖动到源代码中)。相反,你完全可以把属性声明和方法声明手动输入到头文件中,然后建立文本框到它的委托的连接。当然了,尽量使用 Xcode 的自动功能可以帮助你减少输入错误(同时也能减少一点手动输入工作)。
要实现视图控制器,需要做好几件事:为用户的姓名添加一个属性、实现 changeGreeting: 方法、确保用户轻敲 Done 按钮时键盘能够收起来。
为用户的姓名添加一个属性
用户的姓名由一个字符串保存着,首先要为该字符串声明一个属性,这样你的代码才能引用它。请将此声明添加到视图控制器的头文件中(即 HelloWorldViewController.h)。
属性声明是一个指令,它会告诉编译器如何为变量生成存取方法(Access method),例如这里的用来保存用户姓名的变量。(完成添加属性声明之后,你会学到什么是存取方法。)
在本教程中,你无需对故事板文件进行任何改动。接下来的代码比较多,为了给自己腾出必要的工作空间,可以点按 View 按钮中的实用工具按钮关闭实用工具区域(或者点选 View > Utilities > Hide Utilities)。
如何为用户的姓名添加属性声明…
- 在工程导航栏中点选 HelloWorldViewController.h。
- 在 @end 语句之前,为字符串添加一个 @property 语句。
属性声明语句应该是这样的:
@property (copy, nonatomic) NSString *userName;
你可以将这行代码拷贝并粘贴到编辑器面板中,也可以手动输入。如果你要手动输入,就能注意到 Xcode 会针对你的输入提供补全建议。比方说你开始输入 @pro… 时 Xcode 就会猜测你可能想要输入 @property,于是就会在这行语句下面显示一个建议面板,如图:
如果该建议是正确的(如上图的例子),则可以按回车键接受之。
在你继续输入代码时,Xcode 也许会提供一系列建议让你挑选。比如在你输入 NSSt… 的时候,Xcode 可能会显示类似图中的补全建议:
当 Xcode 显示补全列表时,按回车键就可以接受当前高亮显示的那一个语句了。如果高亮显示的并非你所需的语句(比如上图中的例子),可以按方向键在列表中选择合适的项目。
要完成 userName 属性的实现,你还需要让编译器合成相应的存取方法。存取方法是用来读取或设置对象属性的值的方法(有时,存取方法也被称作“getter”和“setter”)。
Xcode 会产生一个警告,提示你必须合成相应的存取方法。在活动查看器里会显示一个黄色的警告符号:
此时,你已经知道 Xcode 的警告是什么,所以不必查看警告的详细信息。若需要查看警告信息的具体内容,你可以点按活动查看器中的黄色警告图标,并在问题导航栏中查看细节:
在本篇教程里,你不会再用到问题导航栏。请点按导航选择条最左边的按钮返回工程导航栏。
接下来,你需要向视图控制器的实现文件(即 HelloWorldViewController.m)中输入代码,让编译器生成存取方法。
如何为用户的姓名属性生成存取方法…
- 在工程导航栏中点选 HelloWorldViewController.m。
- 在 @implementation HelloWorldViewController 这行的下面,输入如下代码:
@synthesize userName = _userName;
输入这段代码之后,Xcode 就不会再警告你缺少存取方法了,于是警告图标也就从活动查看器中消失了。
当编译器遇到 @synthesize 指令时,它实际上会自动为你生成下面两个存取方法:
- (NSString *)userName
- (void)setUserName:(NSString *)newUserName由于在 @synthesize 语句里的 userName 添加了一个下划线,编译器便知道 _userName 是 userName 属性的一个实例变量名称。而你之前没有为名为 _userName 的实例变量进行声明,这段代码就会向编译器请求为它也生成存取方法。
注意:编译器生成存取方法只会在已编译的代码中进行,而不会改动你的源代码文件。
实现 changeGreeting: 方法
在上一个章节“对视图进行调整”里,你配置了 Hello 按钮的动作,在用户轻敲按钮时它便给视图控制器发送一个 changeGreeting: 消息。为了响应此消息,你会让视图控制器在标签上显示一段文字,包含用户刚刚输入的内容。具体而言,changeGreeting: 方法应该:
- 从文本框取回字符串,并将视图控制器的 userName 属性值设置为这个字符串。
- 根据 userName 属性创建一个新字符串,并将其显示在标签上。
如何实现 changeGreeting: 方法…
- 在工程导航栏中点选 HelloWorldViewController.m。
你可能要移动到文件末尾才会看到 Xcode 为你添加的 changeGreeting: 存根实现。
- 将 changeGreeting: 方法的存根实现补全,请输入如下代码:
- (IBAction)changeGreeting:(id)sender {
self.userName = self.textField.text;
NSString *nameString = self.userName;
if ([nameString length] == 0) {
nameString = @”World”;
}
NSString *greeting = [[NSString alloc] initWithFormat:@”Hello, %@!”, nameString];
self.label.text = greeting;
}在 changeGreeting: 方法中有好几个有趣的地方:
- self.userName = self.textField.text; 从文本框取回文字,并将视图控制器的 userName 属性值设置为该文字。
在本篇教程中,你可能不会在其他地方用到存有用户姓名的这个字符串,但仍然需要明白它的作用:它是非常简单的一个模型对象,由视图控制器管理。通常情况下控制器会在自己的模型对象中保存关于应用数据的信息,该数据信息不应该被保存在用户界面元素中,例如 HelloWorld 的文本框。- NSString *nameString = self.userName; 会创建一个新的变量(类型为 NSString)并将它的值设为视图控制器 userName 属性的值。
- @”World” 是一个字符串常量,由一个 NSString 类的实例来表示。如果用户运行应用时没有输入任何文字(即满足 [nameString length] == 0 这个条件),nameString 就会包含字符串“World”。
- initWithFormat: 方法是由 Foundation 框架提供给你的。它根据你提供的格式化字符串的格式创建一个新的字符串(这一点和 C 语言中的 printf函数很相近,你也许对它比较熟悉)。
在格式化字符串中,%@ 代表字符串对象的占位符。双引号中的其他字符则会原封不动地显示在屏幕上。将视图控制器配置为文本框的委托
如果你构建并运行应用,你应该会发现点按按钮时标签显示“Hello World!”字样。可是当你点选了文本框并输入一些文字时,则会发现仍然无法通过点按 Done 来收起键盘。
在一个 iOS 应用中,一旦能够接受文字输入的 UI 元素成为 first responder 就会自动调出键盘。反过来,该元素失去 first responder 状态时键盘就会自动收起。(请回忆一下 first responder 是什么:它是在若干事件中第一个接收通知的对象,例如轻敲文本框就会呼出键盘。)虽然不能直接从你的应用给键盘发送消息,但你可以通过改变文字输入 UI 元素的 first responder 状态来呼出和收起键盘。
而 UITextFieldDelegate 协议是由 UIKit 框架定义的,其中包含了一个 textFieldShouldReturn: 方法,它在用户轻敲回车键(无论回车键的标题被改成什么)是被文本框调用。因为我们已经将视图控制器设为了文本框的委托(在上一章节“对视图进行调整”),你可以通过实现这个方法来强制文本框失去 first responder 状态,给它发送一个 resignFirstResponder 消息即可,通过这个方式就能收起键盘。
注意:协议从根本上讲就是一系列方法的集合。如果某个类遵守(或采用了)某个协议,它也就承诺一定会实现协议中规定的所有方法。(协议也可以包含可选方法。)委托协议指定了对象可能发送给委托的所有消息。
如何配置文本框的委托 HelloWorldViewController …
- 在工程导航栏中点选 HelloWorldViewController.m。
- 实现 textFieldShouldReturn: 方法。
这个方法会让文本框失去 first responder 状态。实现的代码如下:
- (BOOL)textFieldShouldReturn:(UITextField *)theTextField {
if (theTextField == self.textField) {
[theTextField resignFirstResponder];
}
return YES;
}在本应用中,并非一定要包含 theTextField == self.textField 这个检查语句,因为只有一个文本框。不过这个模式非常值得反复使用,因为在某些情况下某个对象可能会成为类型相同的多个对象的委托,你就需要严格区分它们了。
- 在工程导航栏中点选 HelloWorldViewController.h。
- 在 @interface 行的结尾添加 <UITextFieldDelegate>。
你的接口声明应该是这样的:
@interface HelloWorldViewController : UIViewController <UITextFieldDelegate>
…这段声明指定了你的 HelloWorldViewController 类要采用 UITextFieldDelegate 协议。
最终测试应用
构建并运行应用吧。这一次,所有功能应该都已达到我们的预期效果。在 iOS 模拟器中,输入完你的姓名后点按 Done 按钮来收起键盘,然后点按 Hello 按钮在标签里显示“Hello, 你的姓名!”。
如果应用还是没能按照预期效果工作,你就需要排查一下问题了。下一章节“故障排除以及代码检查”里将介绍可能出错的几个地方。
本节回顾
那么,你终于把视图控制器的实现也做好了,你做出了你的第一个 iOS 应用,祝贺你!
如果你的应用仍然无法正常工作,请根据本章节来尝试进行故障排查。如果仍然无法找到错误,请按照本章节最后的代码清单和你的代码进行逐一比对。
代码和编译器警告
你的代码应该能够无警告并通过编译。如果发现有警告出现,建议你将它们当成错误来看待。因为 Objective-C 是个非常灵活的语言,有时候编译器顶多把一些地方判定成警告而不是错误。
检查故事板文件
作为一名开发者,如果发现有东西运行不正常,出于自然的直觉你大概会去检查源代码中的漏洞。但是在 Cocoa Touch 开发中,需要注意另一件事:你的应用中许多配置可能被“编码”到了故事板文件中。打个比方,如果你的连接不正确,应用也不会如预期那样正常工作。
- 如果点按按钮后文字没有更新,则可能是按钮的动作没有和视图控制器正确连接,或者是视图控制器的插座变量没有和文本框或者标签正确连接。
- 如果你点按 Done 按钮后键盘并未收回,则有可能是文本框的委托或者视图控制器的 textField 插座变量没有跟文本框正确连接。请务必检查故事板中文本框的连接情况:按住 Control 键并点按文本框,调出半透明的连接面板。你应该看到 delegate 插座变量以及 textField 引用旁边是带有填充的圆形。
如果你确实连接了委托,那么可能存在某些更加微妙的问题(参看下边的“委托方法名称”)。
委托方法名称
委托中常见的一个错误就是拼错委托方法的名称。就算正确设定了委托对象,但是如果委托没有使用方法实现中的正确名称,则正确的方法就永远也不会被调用。最好的建议是从开发者文档中拷贝和粘贴委托方法的声明,比如 textFieldShouldReturn:。
代码清单
在这个部分,HelloWorldViewController 类的接口和实现文件都被完全罗列出来。需要注意的是本代码清单不包含由 Xcode 模板提供的其他方法实现以及代码注释。
接口文件:HelloWorldViewController.h
#import <UIKit/UIKit.h>
@interface HelloWorldViewController : UIViewController <UITextFieldDelegate>
@property (weak, nonatomic) IBOutlet UITextField *textField;
@property (weak, nonatomic) IBOutlet UILabel *label;
@property (nonatomic, copy) NSString *userName;- (IBAction)changeGreeting:(id)sender;
@end
实现文件:HelloWorldViewController.m
#import “HelloWorldViewController.h”
@implementation HelloWorldViewController
@synthesize textField=_textField;
@synthesize label=_label;
@synthesize userName=_userName;
- (BOOL)textFieldShouldReturn:(UITextField *)theTextField {
if (theTextField == self.textField) {
[theTextField resignFirstResponder];
}
return YES;
}- (IBAction)changeGreeting:(id)sender {
self.userName = self.textField.text;NSString *nameString = self.userName;
if ([nameString length] == 0) {
nameString = @”World”;
}
NSString *greeting = [[NSString alloc] initWithFormat:@”Hello, %@!”, nameString];
self.label.text = greeting;
}
@end