第2章 创建你的第一个iOS应用程序
开发优秀的iPhone/iPad应用程序,需要大量的学习和实践。不过,有了Xcode工具和iOS SDK,开发一个简单可用的程序并非难事。结合第1章搭建的开发环境,本章就可以创建你的首个iOS应用程序。
在创建应用程序的过程中,可能会遇到很多不明白的问题,不要担忧,只需要根据本章的介绍“照葫芦画瓢”,不理解的地方可以跳过,继续往下进行。本章的目的是,使你对基础开发过程有初步了解。
2.1 应用程序的实现目标
本章要实现的应用程序主要有三个用户界面元素(如图2-1所示)。
·文本栏(用户输入信息);
·标签(显示信息);
·按钮(让应用程序在标签中显示信息)。
图2-1 应用程序运行效果
运行应用程序,单击文本栏会调出系统提供的键盘。使用键盘输入姓名后,单击“Done”键将键盘隐藏,然后单击“Hello”按钮,即可在文本栏和按钮之间的标签中看到字符串“Hello,你的姓名!”。
注意 尽管本节显示的是iPhone用户界面,但使用的工具和技巧与开发iPad应用程序所用到的完全相同;所以即使打算只开发iPad程序,仍可以将本章当作入门指南。
2.2 入门的开始
要创建本章的iOS应用程序,需要安装Xcode 5.0或更高版本。Xcode是Apple的集成开发环境(又称IDE),用于iOS和Mac OS X的开发。在Mac上安装Xcode的同时,也会安装iOS SDK,它包含iOS平台的编程接口。
2.2.1 新建一个Xcode项目
新建一个Xcode项目主要步骤如下。
步骤1 打开Xcode(默认位置在“/应用程序”)。
如果从未在Xcode中创建或打开过项目,会看到一个“Welcome to Xcode”窗口,如图2-2所示。
图2-2 “Welcome to Xcode”窗口
如果曾在Xcode中创建或打开过项目,会看到一个项目窗口,而不是“Welcome to Xcode”窗口。
步骤2 新建应用程序模板窗口。
在“Welcome to Xcode”窗口中,单击“Create a new Xcode project”,或选取“File”→“New”→“New project”。Xcode将打开一个新窗口并显示对话框,如图2-3所示,从中选取一个模板。
图2-3 新建应用程序模板对话框
Xcode中内建了一些应用程序模板,可以使用这些模板开发常见类型的iOS应用程序。例如“Tabbed”模板可以创建类似iTunes的应用程序,“Master-Detail”模板可以创建类似邮件的应用程序。
步骤3 新项目选项填写窗口。
在图2-3对话框左边的iOS部分选择“Application”,选择“Single View Application”,然后单击“Next”。一个新对话框会出现,如图2-4所示,提示为应用程序命名,并为项目选取附加选项。
步骤4 填写“Product Name”、“Company Identifier”和“Class Prefix”等栏位。
这里可以使用以下值。
·Product Name:HelloWorld
·Company Identifier:公司标识符(如果没有,可以使用edu.self)
·Class Prefix:HelloWorld
注意 Xcode使用输入的产品名称来命名项目和应用程序,使用类前缀名称来命名创建的类。例如,Xcode会自动创建一个应用程序委托类,命名为HelloWorldAppDelegate。如果输入不同的值作为类前缀,则应用程序委托类将命名为“你的类前缀名称AppDelegate”。
图2-4 新项目选项填写对话框
步骤5 在“Device”弹出式菜单中选取“iPhone”。
步骤6 选取“Use Storyboard”和“Use Automatic Reference Counting”选项,但不选定“Include Unit Tests”选项。
注意 在Xcode 5之前,此项需要对二者进行选择,在Xcode 5之后,系统默认都是“Use Storyboard”。
步骤7 单击“Next”。此时出现另一个对话框,用来指定项目存储的位置。
不选定“Source Control”选项,然后单击“Create”。Xcode在工作区窗口中打开新项目。
从结构布局上,Xcode的工作区窗口可以按如图2-5所示布局进行分类,不同区域担负着不同职责功能。在接下来的介绍中,你将会用到窗口中标识的按钮和区域。
如果工作区窗口中的实用工具区域已打开(如图2-5窗口中所示),可以暂时把它关闭,因为稍后才会用到它。最右边的“View”按钮可控制实用工具区域。当实用工具区域可见时,该按钮为。如有需要,单击“View”按钮关闭实用工具区域。
2.2.2 在模拟器中查看应用程序的效果
即使不编写任何代码,也可以构建应用程序,并在模拟器(已包含在Xcode中)中运行。模拟器可模拟应用程序在iPhone设备上运行,让你初步了解应用程序的外观和行为。
按照以下步骤就可以在模拟器中运行你的应用程序。
步骤1 在Xcode工具栏的“Scheme”弹出式菜单中选定“HelloWorld”→“iPhone 7.0Simulator”选项。如果弹出式菜单中该选项未被选定,可以把它打开,然后从菜单中选取“iPhone 7.0Simulator”,如图2-6所示。
图2-5 工作区窗口的划分
图2-6 设置运行模拟设备
步骤2 单击Xcode工具栏中的“Run”按钮,或选取“Product”→“Run”。
在Xcode生成项目后,模拟器应该会自动启动。因为指定的是iPhone产品而非iPad产品,模拟器会显示一个类似iPhone的窗口。在模拟的iPhone屏幕上,用模拟器打开你的应用程序,外观如图2-7所示。
图2-7 程序运行效果
此刻,你的应用程序只显示一个空白的画面。要了解空白画面是如何生成的,需要了解代码中的对象,以及它们如何紧密协作来启动应用程序。
现在,退出模拟器(选取“iOS Simulator”→“Quit iOS Simulator”,注意不是退出Xcode)。
2.3 启动一个应用程序
由于项目是基于Xcode模板开发的,在运行应用程序时,大部分基本的应用程序环境已经自动建立。例如,Xcode创建一个应用程序对象(以及其他一些东西)来建立运行循环(运行循环将输入源寄存,并将输入事件传递给应用程序)。该工作大部分是由UIApplicationMain函数完成的,此函数由UIKit框架提供,在项目的main.m源文件中自动调用。
注意 UIKit框架提供应用程序构建和管理其用户界面所需的全部类。UIKit框架只是Cocoa Touch提供的面向对象的众多框架中的一个,而Cocoa Touch是所有iOS应用程序的应用环境。
2.3.1 探究main.m源文件
确定项目导航器已在导航器区域中打开,项目导航器显示项目中的所有文件。如果项目导航器未打开,单击导航器选择栏最左边的按钮,如图2-8所示。
图2-8 导航器选择栏
单击项目导航器中Supporting Files文件夹旁边的展示三角形打开文件夹,选择main.m,在Xcode工作区窗口的主编辑器区域打开源文件,如图2-9所示。
图2-9 main.m文件
main函数调用自动释放池(autorelease pool)中的UIApplicationMain函数。
@autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([HelloWorldAppDelegate class])); }
@autoreleasepool语句支持“自动引用计数”(ARC)系统。ARC可自动管理应用程序的对象生命周期,确保对象在需要时一直存在,直到不再需要。
调用UIApplicationMain会创建一个UIApplication类的实例和一个应用程序委托的实例(在本节中,应用程序委托是HelloWorldAppDelegate,由Single View模板提供)。应用程序委托的主要作用是提供呈现应用程序内容的窗口,在应用程序呈现之前,应用程序委托也执行一些配置任务。
注意 委托是一种设计模式,在此模式中,一个对象代表另一个对象,或与另一个对象协调工作。
2.3.2 分析属性列表文件
在iOS应用程序中,窗口对象为应用程序的可见内容提供容器,协助将事件传递到应用程序对象,协助应用程序对设备的摆放方向做出响应。窗口本身是不可见的。调用UIApplicationMain也会扫描应用程序的Info.plist文件。Info.plist文件为信息属性列表,即键和值配对的结构化列表,它包含应用程序的信息,例如名称和图标。
在项目导航器的Supporting Files文件夹中,选择HelloWorld-Info.plist。Xcode在窗口的编辑器区域打开Info.plist文件。
在本节中,你不需要查看Supporting Files文件夹中的文件,因此可以在项目导航器中关闭此文件夹以避免分散注意力。同样,单击Supporting Files文件夹图标旁边的展示三角形以关闭该文件夹。
因为你已选取在项目中使用串联图,所以Info.plist文件还包含应用程序对象应该载入的串联图的名称。串联图包含对象、转换以及连接的归档,它们定义了应用程序的用户界面。
在HelloWorld应用程序中,将串联图文件命名为MainStoryboard.storyboard(注意,Info.plist文件只显示名称的第一部分)。在应用程序启动时,载入MainStoryboard.storyboard,接着根据它对初始视图控制器进行实例化。
注意 视图控制器是管理区域内容的对象,而初始视图控制器是应用程序启动时载入的第一个视图控制器。
2.3.3 查看串联图
视图是一个对象,它在屏幕的矩形区域中绘制内容,并处理由用户触摸屏幕所引起的事件。一个视图也可以包含其他视图,这些视图称为分视图。一个视图在添加了一个分视图后,就称为父视图,这个分视图称为子视图。父视图、子视图以及子视图的子视图(如有)形成一个视图层次。一个视图控制器只管理一个视图层次。
注意 “模型-视图-控制器”(Model-View-Controller,MVC)设计模式定义了应用程序对象的三种角色,HelloWorld应用程序中的视图和视图控制器体现了其中的两种,而第三种为模型对象。在MVC中,模型对象表示数据(例如日历应用程序中的待办事项或绘图程序中的图形),视图对象知道如何显示模型对象所表示的数据,控制器对象充当模型和视图的媒介。在HelloWorld应用程序中,模型对象为字符串,用来保存用户输入的名称。现在不需要了解更多有关MVC的信息,但最好开始思考应用程序中的对象如何扮演不同的角色。
在接下来的步骤中,要给由HelloWorldViewController管理的视图添加三个分视图,以创建视图层次;这三个子视图分别表示文本栏、标签和按钮。可以在串联图中看到视图控制器及其视图的模样。
在项目导航器中选择MainStoryboard.storyboard。Xcode在编辑器区域打开串联图。串联图对象后面的区域看起来像图纸,该区域称为画布。打开默认串联图后,工作区窗口如图2-10。串联图包括场景和过渡。场景代表视图控制器,过渡则表示两个场景之间的转换。
图2-10 串联图
因为Single View模板提供一个视图控制器,应用程序中的串联图只包含一个场景,所以没有过渡。画布上指向场景左侧的箭头是“初始场景指示器”(initial scene indicator),它标识应用程序启动时应该首先载入的场景(通常初始的场景就是初始视图控制器)。
在画布上看到的场景为“Hello World View Controller”,它是由HelloWorldViewController对象来管理的。Hello World View Controller场景由一些项目组成,显示在Xcode大纲视图(在画布和项目导航器之间的面板)。视图控制器由以下项目组成。
·第一响应器占位符对象。“First Responder”是一个动态占位符,在应用程序运行时,它应该是第一个接收各种事件的对象。这些事件包括以编辑为主的事件(例如轻按文本栏以调出键盘)、运动事件(例如摇晃设备)和操作消息(例如当用户轻触按钮时该按钮发出的消息)等。本节不会涉及第一响应器的任何操作。
·Exit占位符对象,用于展开序列。默认情况下,当用户使子场景消失时,该场景的视图控制器展开(或返回)父场景,即转换为该子场景的原来场景。不过,Exit对象使视图控制器能够展开任意一个场景。
·HelloWorldViewController对象。串联图载入一个场景时,会创建一个视图控制器类的实例来管理该场景。
·一个视图。列在视图控制器下方(要在大纲视图中显示此视图,可能要打开“Hello World View Controller”旁边的展示三角形)。此视图的白色背景就是在Simulator中运行该应用程序时所看到的背景。
注意 应用程序的窗口对象在串联图中并未表示出来。
在画布中,场景下方的区域称为场景台。图2-10中,场景台显示了视图控制器的名称,即“Hello World View Controller”。其他时候,场景台可包含代表第一响应器、Exit占位符对象和视图控制器对象的图标。
2.4 检查视图控制器及其视图
一个视图控制器负责管理一个场景,而一个场景代表一个内容区域。在该区域中看到的内容,是在视图控制器的视图中定义的。在本节中,可以更仔细地查看由HelloWorldViewController所管理的场景,并学习如何调整视图的背景颜色。
2.4.1 如何使用检查器
应用程序启动时,载入主串联图文件,然后实例化初始视图控制器。初始视图控制器管理用户打开应用程序时看到的第一个场景。因为Single View模板只提供一个视图控制器,该视图控制器自动设定为初始视图控制器。可以使用Xcode检查器来验证视图控制器的状态,并查看关于它的其他信息。
打开检查器的步骤如下。
步骤1 如有需要,单击项目导航器中的MainStoryboard.storyboard,在画布上显示场景。
步骤2 在大纲视图中,选择“Hello World View Controller”,列在“First Responder”下方。工作区窗口外观如图2-11所示。
图2-11 检查器工作场景
注意 场景和场景台都有蓝色外框,并且已选定场景台中的视图控制器对象。
步骤3 单击工具栏最右边的“View”按钮,在窗口右边显示实用工具区域(或者选取“View”→“Utilities”→“Show Utilities”)。
步骤4 在实用工具区域中单击“Attributes”按钮,打开Attributes检查器。
“Attributes”按钮位于实用工具区域顶部的检查器选择栏中,从左边数第四个按钮,如图2-12所示。在Attributes检查器的“View Controller”部分,可以看到已选定“Initial Scene”选项。
注意 确定“Initial Scene”选项一直是选定的状态。如果取消选定这个选项,初始场景指示器会从画布中消失。
图2-12 检查器选择栏
2.4.2 更改视图的背景颜色
视图在Simulator中运行应用程序时提供的是白色背景。要确定应用程序工作正常,可以将视图的背景设定为白色以外的其他颜色,并再次在Simulator中运行应用程序来验证新颜色的显示。在更改视图的背景之前,要确定串联图仍打开在画布上。
注意 如有需要,单击项目导航器中的MainStoryboard.storyboard,在画布上打开串联图。
1.设定视图的背景颜色
设定视图控制器的视图背景颜色的步骤如下。
步骤1 在大纲视图中,单击“Hello World View Controller”旁边的展示三角形(如果还未打开),然后选择“View”。Xcode在画布上高亮显示视图区域。
步骤2 在实用工具区域顶部的检查器选择栏中单击“Attributes”按钮,打开Attributes检查器(如果还未打开)。
步骤3 在Attributes检查器中,单击Background弹出式菜单(如图2-13所示)中的白色矩形,打开Colors窗口,矩形显示该项目当前的背景颜色。
注意 并非单击白色矩形,而是单击“Default”打开弹出式菜单,从中选取“Other”。
步骤4 在Colors窗口中,选择白色以外的颜色。注意,在选择视图时Xcode高亮显示该视图,所以画布上的颜色可能和Colors窗口中的颜色看起来不同。
步骤5 关闭Colors窗口。
步骤6 单击“Run”按钮或选取“Product”→“Run”,在Simulator中测试你的应用程序,效果如图2-14所示。确定Xcode工具栏中的Scheme弹出式菜单仍然显示“HelloWorld”→“iPhone 7.0Simulator”。
图2-13 Background菜单
图2-14 背景颜色设置为红色的效果
注意 在运行应用程序前,可以不必存储你的工作,因为单击“Run”或选取“Product”→“Run”时,Xcode会自动存储改过的文件。
2.恢复视图的背景颜色
恢复视图的背景颜色的步骤如下。
步骤1 在Attributes检查器中,单击箭头打开Background弹出式菜单。
注意 Background弹出式菜单中的矩形已改变成显示在Colors窗口中所选取的颜色。如果单击的是有颜色的矩形而不是箭头,Colors窗口会重新打开。要想重新使用视图原来的背景颜色,从Background菜单中选取该颜色即可,比从Colors窗口中找要简单得多。
步骤2 在Background弹出式菜单中选取“Recently Used Colors”中列出的白色方块。
步骤3 单击“Run”按钮编译和运行应用程序,并存储所做的更改。
步骤4 验证了应用程序重新显示白色背景后,退出Simulator。
在运行应用程序时,Xcode可能会在工作区窗口的底部打开调试区。由于本节不会用到该面板,可以将其关闭,以腾出更多空间。
2.5 对视图进行配置和管理
Xcode提供了对象库,可以将库中的对象添加到串联图文件。其中,一些对象属于视图中的用户界面元素,例如按钮和文本栏;其他对象为高级对象,例如视图控制器和手势识别器。
Hello World View Controller场景已经包含一个视图,现在需要添加一个按钮、一个标签和一个文本栏。然后在这些元素和视图控制器类之间建立连接,使元素提供你想要的行为。
2.5.1 新增用户界面元素
将对象库中的用户界面(UI)元素拖移到画布的视图中,以便添加用户界面元素。将UI元素添加到视图后,可以适度移动它们的位置和调整大小。
1.将UI元素添加到视图并适当进行布局
将元素添加到视图的步骤如下。
步骤1 如有需要,选择项目导航器中的MainStoryboard.storyboard,在画布上显示Hello World View Controller场景。
步骤2 如有需要,打开对象库。
图2-15 对象库按钮
对象库出现在实用工具区域的底部。如果看不到对象库,可以单击其按钮,如图2-15所示。
步骤3 在对象库中,从“Objects”弹出式菜单中选取“Controls”。
Xcode将控制列表显示在弹出式菜单下方。该列表显示每个控制的名称、外观及其功能的简短讲述。
步骤4 添加视图对象。
从列表中拖拽一个文本栏、一个圆角矩形按钮和一个标签到视图上,一次添加一个。
步骤5 调整文本栏位置。
在视图中,拖移文本栏将其放置在视图的左上角附近。在移动文本栏或任何其他UI元素时,会出现蓝色的虚线(称为对齐参考线),有助于将项目与视图的中心和边缘对齐。当看到视图的左方和上方对齐参考线(如图2-16所示)时,停止拖移文本栏。
图2-16 添加视图对象
步骤6 在视图中调整文本栏的大小。
图2-17 调整文本栏大小
通过拖移控制柄(显示在元素边框上的小白方块)调整UI元素的大小。一般情况下,在画布上或在大纲视图中选择一个元素,该元素的调整大小控制柄就会显露出来。在本例中,因为刚刚停止拖移,文本栏应该已被选定。如果文本栏外观如图2-17所示,就可以调整它的大小;否则要在画布上或在大纲视图中选择它。
拖移文本栏右侧的控制柄,直到视图最右侧的对齐参考线出现。当画布如图2-18所示时,停止调整文本栏大小。
图2-18 需要调整大小的文本栏
步骤7 在选定文本栏时,打开Attributes检查器(如有需要)。
步骤8 在Text Field Attributes检查器顶部附近的Placeholder栏中输入Your Name。
顾名思义,Placeholder栏提供的浅灰色文本是为了帮助用户理解在文本栏中能够输入何种信息。在运行的应用程序中,用户只要在文本栏内轻按鼠标,占位符文本就会立即消失。
步骤9 在Text Field Attributes检查器中,单击中间的“Alignment”按钮,使文本栏的文本居中显示。
步骤10 在视图中,拖移标签到文本栏下方,并使其左边缘和文本栏的左边缘对齐。
步骤11 调整标签的宽度。
图2-19 调整标签的宽度
拖移标签右侧的控制柄,使标签与文本栏同宽。比起文本栏,标签有更多的控制柄用于调整标签的高度和宽度。现在并不是要更改标签的高度,因此不要拖移标签四个角的控制柄。要拖移标签右侧中间的控制柄调整宽度,如图2-19所示。
步骤12 在Label Attributes检查器中,单击中间的“Alignment”按钮,使出现在标签中的文本居中显示。
步骤13 拖移按钮使其靠近视图底部并且水平居中。
步骤14 在画布上,双击该按钮,然后输入文本Hello。
添加文本栏、标签和按钮UI元素,并对布局做出建议的修改后,如图2-20所示。
可能注意到,当将文本栏、标签和按钮添加到背景视图时,Xcode在Constraints大纲视图中插入了项目。Cocoa Touch具有一个自动布局系统,用来定义用户界面元素的布局约束。这些约束定义用户界面元素之间的关系,当其他视图的大小改变或设备摆放方向改变时,该关系影响各界面元素如何改变其位置和几何形状。现在不要改变添加到用户界面的视图的默认约束。
此外,可以对文本栏进行修改,使文本栏的行为符合用户的期望。首先,因为用户要输入自己的姓名,确保iOS对用户键入的每个英文单词执行首字母大写。其次,确保与文本栏相关联的键盘配置为输入姓名(而不是数字),并且键盘显示“Done”按钮。
图2-20 应用程序界面布局图
注意 这些更改所遵循的原则是:因为在设计时已知道文本栏将包含何种类型的信息,可以配置文本栏使它运行时的外观和行为符合用户的任务。这些配置都可以在Attributes检查器中修改。
2.对文本栏进行配置
对文本栏进行配置的方法很简单。首先在视图中选择文本栏,在Text Field Attributes检查器中进行以下选择。
·在“Capitalization”弹出式菜单中选取“Words”。
·确保“Keyboard”弹出式菜单设定为“Default”。
·在“Return Key”弹出式菜单中选取“Done”。
3.运行应用程序
在Simulator中运行应用程序,以确定所添加的UI元素外观正如所期望的样子。如果单击“Hello”按钮,该按钮应该高亮显示;如果在文本栏内单击,键盘应该出现。不过,这时按钮没有任何功能,标签还是空的,而且键盘出现后无法使它消失。要添加此功能,需要在UI元素和视图控制器之间进行适当的连接。下面将说明如何建立连接。
注意 因为只是在Simulator中(而不是在设备上)运行应用程序,所以通过单击来激活控制,而不是用手轻按的方式。
2.5.2 为按钮增添一个动作
当用户激活一个UI元素时,该元素可以向知道如何执行相应操作方法的对象发送一则操作消息,例如“将此联系人添加到用户的联系人列表”。这种互动是目标-操作机制的一部分,该机制是另一种Cocoa Touch设计模式。
在本节中,当用户单击“Hello”按钮时,想要发送一则“更改问候语”的消息(操作)给视图控制器(目标)。视图控制器通过更改其管理的字符串(即模型对象)来响应此消息。然后,视图控制器更新在标签中显示的文本,以反映模型对象值的变动。
使用Xcode,可以将操作添加到UI元素,并设置其相应的操作方法。方法是按住Ctrl键并将画布上的元素拖移到源文件中的合适位置(通常是类扩展在视图控制器的实现文件中)。串联图将通过这种方式创建的连接归档存储下来。应用程序在载入串联图时会恢复这些连接。
为按钮添加动作的步骤如下。
步骤1 如有需要,选择项目导航器中的MainStoryboard.storyboard,将场景显示在画布上。
步骤2 在Xcode工具栏中,单击“Utilities”按钮以隐藏实用工具区域,单击“Assistant Editor”按钮以显示辅助编辑器面板。“Assistant Editor”按钮为中间的编辑器按钮。
步骤3 确定“Assistant”显示视图控制器的实现文件,即HelloWorldViewController.m。
如果显示HelloWorldViewController.h,在项目导航器中选择HelloWorldViewController.m。
步骤4 对象连接。
在画布上按住Ctrl键将“Hello”按钮拖移到HelloWorldViewController.m中的类扩展。
实现文件中的类扩展是申明类的专有属性和方法的地方。视图控制器的Xcode模板包含实现文件中的类扩展。以HelloWorld项目为例,类扩展看起来像这样:
@interface HellowWorldViewController() @end
按住Ctrl键不放,将按钮拖移到辅助编辑器中的实现文件。随着你按住Ctrl键拖移,看到的应该如图2-21所示。
松开Ctrl键停止拖移后,Xcode会显示一个弹出式窗口,在窗口中可以设置操作连接,如图2-22所示。
图2-21 将按钮拖移到辅助编辑器中的实现文件
图2-22 对象连接
注意 如果在HelloWorldViewController.m类扩展区域以外的其他地方松开Ctrl键并停止拖移,可能会看到不同类型的弹出式窗口,或者什么都没有。如果出现这种情况,要在画布上的视图内部单击以关闭弹出式窗口,并再试一次按住Ctrl键拖移。
步骤5 绑定处理。
在弹出式窗口中,按如下操作配置按钮的操作连接。
·在“Connection”弹出式菜单中选取“Action”。
·在“Name”栏中输入“changeGreeting:”(包括冒号)。在后面步骤中将实施该方法,让它把用户输入文本栏的文本载入,然后在标签中显示。
·确定“Type”栏包含id。id数据类型可指任何Cocoa对象。在这里使用id是因为无论哪种类型的对象发送消息都没有关系。
·确定“Event”弹出式菜单包含“Touch Up Inside”。指定“Touch Up Inside”事件是因为想在用户触摸按钮后提起手指时发送消息。
·确定“Arguments”弹出式菜单包含“Sender”。
配置完操作连接后,弹出式窗口如图2-23所示。
图2-23 绑定处理
步骤6 在弹出式窗口中单击“Connect”。
Xcode为新的changeGreeting:方法添加一个存根实现,并在该方法的左边显示一个带有填充的圆圈,以标示已经建立连接。
步骤7 按住Ctrl键将“Hello”按钮拖移到HelloWorldViewController.m文件中的类扩展。
目前,你完成两件事情:通过Xcode将合适的代码添加到视图控制器类中(也就是HelloWorldViewController.m中),并在按钮和视图控制器之间创建了连接。具体来说,Xcode做了以下事情。
1)在HelloWorldViewController.m中,将以下操作方法声明添加到类扩展。
- (IBAction)changeGreeting:(id)sender;
2)将以下存根方法添加到实现区域。
- (IBAction)changeGreeting:(id)sender { }
注意 IBAction是一个特殊关键词,用于告诉Xcode将一个方法作为目标-操作连接的操作部分来处理。IBAction被定义为void。
操作方法中的sender参数指向发送操作消息的对象(在本节中,发送对象为按钮)。它在按钮和视图控制器之间创建了连接。
接下来,在视图控制器和其他两个UI元素(即标签和文本栏)之间创建连接。
2.5.3 为文本框和标签创建插座
插座(Outlet)表明了两个对象之间的连接。当一个对象(例如视图控制器)要和它包含的对象(例如文本栏)进行通信时,必须将被包含的对象指定为Outlet。应用程序在运行时会恢复在Xcode中创建的Outlet,从而使对象在运行时可以互相通信。
在本节中,将视图控制器从文本栏获取用户的文本,然后将文本显示在标签上。为确保视图控制器可以和这些对象通信,就必须在它们之间创建Outlet连接。
为文本栏和标签添加Outlet的步骤与添加按钮操作的步骤非常相似。开始之前,要确定主串联图文件仍然显示在画布上,而HelloWorldViewController.m在辅助编辑器中保持打开。
1.为文本栏添加Outlet
为文本栏添加Outlet的步骤如下。
步骤1 按住Ctrl键将视图中的文本栏拖移到实现文件中的类扩展。
随着按住Ctrl键进行拖移,会看到如图2-24所示的窗口。
图2-24 为文本栏添加Outlet
在类扩展内部的任何地方松开Ctrl键并停止拖移都没有关系。在本节中,文本栏和标签的Outlet声明出现在“Hello”按钮的方法声明上。
步骤2 为文本设置Outlet弹出窗口。
在松开Ctrl键并停止拖移时出现的弹出式窗口中,按如下操作配置文本栏的连接。
·确定“Connection”弹出式菜单包含“Outlet”。
·在“Name”栏中键入“textField”。可以为Outlet随意命名,但是如果其名称与其表示的项目有关联,会使代码更便于阅读。
·确定“Type”栏包含“UITextField”。将“Type”栏设定为“UITextField”可确保Xcode将Outlet仅与文本栏连接。
·确定“Storage”弹出式菜单包含默认值“Weak”。
完成这些设置后,将会看到如图2-25所示的弹出式窗口。
图2-25 为文本设置Outlet后的弹出窗口
步骤3 在弹出式窗口中,单击“Connect”。
在这个过程中,通过为文本栏添加Outlet,你完成了两件事情。
1)Xcode将合适的代码添加到视图控制器类的实现文件(HelloWorldViewController.m)。具体来说,Xcode将以下声明添加到类扩展。
@property (weak, nonatomic) IBOutlet UITextField *textField;
注意 IBOutlet是一个特殊关键词,仅用于告诉Xcode将对象作为Outlet处理。它实际上被定义为什么都不是,因此在编译时不起作用。
2)Xcode在视图控制器和文本栏之间建立了连接。
通过在视图控制器和文本栏之间建立连接,用户输入的文本可以传递给视图控制器。和处理changeGreeting:方法声明一样,Xcode在文本栏声明的左边显示带有填充的圆圈,以表示已经建立连接。
注意 较早版本的Xcode使用的方式是:按住Ctrl键拖移,将@synthesize指令添加到所声明的每个属性的实现块。因为编译器自动合成存取方法,所以这些指令是不必要的,可以放心地将它们全部删除。
2.为标签添加Outlet
现在为标签添加Outlet并配置连接。在视图控制器和标签之间建立连接,会让视图控制器以字符串(该字符串包含用户输入的文本)来更新标签。完成此任务的步骤与为文本栏添加Outlet的步骤相同,只不过在配置时要做适当修改(确定HelloWorldViewController.m仍然显示在辅助编辑器中)。具体步骤如下。
步骤1 按住Ctrl键将视图中的标签拖移到辅助编辑器中的HelloWorldViewController.m的类扩展。
步骤2 为标签设置Outlet弹出窗口。
在松开Ctrl键并停止拖移时出现的弹出式窗口中配置标签的连接。
·确定“Connection”弹出式菜单包含“Outlet”。
·在“Name”栏中键入“label”。
·确定“Type”栏包含“UILabel”。
·确定“Storage”弹出式菜单包含“Weak”。
步骤3 在弹出式窗口中,单击“Connect”。
2.5.4 打开Connections检查器验证连接
到此为止,一共创建了三种到视图控制器的连接:1)按钮的操作连接;2)文本栏的Outlet连接;3)标签的Outlet连接。可以在Connections检查器中验证这些连接(图2-26)。
图2-26 Connections检查器的设置
为视图控制器打开Connections的步骤如下。
步骤1 单击标准编辑器按钮以关闭辅助编辑器,并切换到标准编辑器视图。标准编辑器按钮是最左边的“Editor”按钮。
步骤2 单击“Utilities”视图按钮打开实用工具区域。
步骤3 在大纲视图中选择“Hello World View Controller”。
步骤4 在实用工具区域显示Connections检查器。点击检查器选择栏最右边的按钮,图标是。
在Connections检查器中,Xcode显示了所选对象(在本例中为视图控制器)的连接。如图2-26所示。可以发现,在视图控制器和其视图之间,除了创建的三个连接之外,还有一个连接。Xcode提供了视图控制器和其视图之间的默认连接,不必用任何方式访问它。
2.5.5 对文本框进行委托处理
还需要在应用程序中建立另一个连接,即将文本栏连接到指定的委托对象上。本节将视图控制器用作文本栏的委托。
首先需要为文本栏指定一个委托对象。因为当用户轻按键盘中的“Done”按钮时,文本栏发送消息给它的委托(委托是代表另一个对象的对象)。在后面的步骤中,将使用与此消息相关联的方法让键盘消失。
注意 确定串联图文件已在画布上打开。否则在项目导航器中选择MainStoryboard.storyboard。
设定文本栏的委托的方法如下。在视图中,按住Ctrl键将文本栏拖移到场景台中的黄色球体(黄色球体代表视图控制器对象)松开Ctrl键并停止拖移时,将会看到如图2-27所示的界面。
图2-27 设定文本栏的委托
在出现的半透明面板的“Outlets”部分中选择“delegate”,以达到关联对象的作用。
2.5.6 让应用程序具有辅助功能
Apple的创新性读屏技术VoiceOver是一个重要的辅助功能。使用VoiceOver,用户可以在不看屏幕的情况下导航和控制应用程序的各部分。通过触摸用户界面中的控制或其他对象,用户可以知道它们的位置、可以执行的操作以及执行某些操作后将发生什么。
可以将一些辅助功能属性添加到用户界面中的任何视图。这些属性包括视图的当前值(例如文本栏中的文本)、标签、提示以及其他特征。就HelloWorld应用程序而言,我们将要给文本栏添加一个提示功能。
注意 所添加的任何辅助功能文本都应该本地化。
添加辅助功能的步骤如下。
步骤1 在项目导航器中选择串联图文件(Base Internationalization)。
步骤2 选择文本栏进行关联。
步骤3 在Identity检查器“Accessibility”部分的Hint栏中键入提示内容。
如图2-28所示,其中Hint栏中键入“Type your name”,这是光标移动到文本栏时,将会自动提示的内容。
图2-28 添加辅助功能
步骤4 单击“Run”测试应用程序。
单击“Hello”按钮时,应该看到它高亮显示。如果在文本栏中点按击会出现键盘,可以输入文本,但还没有办法让键盘消失。要让键盘消失,必须实施相关的委托方法。
2.6 使用视图控制器完成应用程序
使用视图控制器包括这几部分:1)为用户姓名添加属性;2)实现changeGreeting:方法;3)把视图控制器作为输入文本框的委托,确保用户轻按“Done”时键盘消失。
2.6.1 给用户的名称添加属性
只有为保存用户姓名的字符串添加属性声明,你的代码才能引用该字符串。因为此属性必须是公共的,即对客户端和子类为可见,所以要将此声明添加到视图控制器的头文件(即HelloWorldViewController.h)。公共属性表示你打算如何使用这一类的对象。
属性声明是一个指令,它告诉编译器如何为变量(例如用来保存用户姓名的变量)生成存取方法。(添加属性声明后,你将了解到有关存取方法的信息。)
到此为止,不需要对串联图文件做出任何进一步的修改。要腾出更多空间以按照以下步骤来添加代码,再次单击“Utilities View”按钮来隐藏实用工具区域(或者选取“View”→“Utilities”→“Hide Utilities”)。
为用户姓名添加属性声明的步骤如下。
在项目导航器中选择HelloWorldViewController.h。在@end语句前,为字符串编写一个@property语句。属性声明应该是这样的:
@property (copy, nonatomic) NSString *userName;
可以复制和粘贴以上代码,也可以在编辑器面板中键入以上代码。如果你决定键入代码,注意,Xcode会根据键入内容提供自动补齐的建议。例如,开始键入“@prop...”,Xcode猜测你想要键入@property,因此会在内联建议面板中显示一个符号,如图2-29所示。
图2-29 属性的声明
如果建议合适,按下Return键接受建议。随着你的继续键入,Xcode可能提供一个建议列表供你选取。例如键入“NSSt...”,Xcode可能显示如图2-30所示的补齐列表。
当Xcode显示补齐列表时,按下Return键以接受高亮显示的建议。如果高亮显示的建议不正确,可使用箭头键从列表中选择合适的项目。
编译器自动为你声明的任何属性合成“存取方法”。存取方法是一种获取或设定一个对象属性值的方法(因此,存取方法也称为“getter”和“setter”)。例如,编译器为刚刚声明的userName属性生成以下getter和setter声明及其实现:
- (NSString *)userName; - (void)setUserName:(NSString *)newUserName;
图2-30 Xcode提供的建议列表
编译器自动声明专有实例变量以支持每一个声明的属性。例如,编译器声明_userName实例变量以支持userName属性。
注意 编译器将生成的存取方法添加到编译代码,而不是添加到你的源代码中。
2.6.2 实现changeGreeting:方法
之前我们已经配置了“Hello”按钮,在用户轻按该按钮时,发送changeGreeting:消息给视图控制器。作为响应,需要视图控制器将用户在文本栏中输入的文本显示在标签中。具体来说,changeGreeting:方法基本实现机制如下。
·从文本栏取回字符串,并将视图控制器的userName属性设定为此字符串。
·基于userName属性创建新的字符串,并将其显示在标签中。
实施changeGreeting:方法的步骤如下。
步骤1 如有需要,在项目导航器中选择HelloWorldViewController.m。
可能需要滚动到文件的末尾才能看到changeGreeting:存根实现,它是Xcode添加的。
步骤2 添加以下代码完成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:方法中有几项值得注意。
1)self.userName=self.textField.text;从文本栏取回文本,并将视图控制器的userName属性设定为该结果。
在本节中,在其他任何地方都不会用上那个保存着用户姓名的字符串,但重要的是记住它的角色:这正是视图控制器所管理的非常简单的模型对象。一般情况下,控制器应在它自己的模型对象中维护应用程序数据的相关信息。换句话说,应用程序数据不应储存在用户界面元素(例如HelloWorld应用程序的文本栏)中。
2)NSString*nameString=self.userName;创建一个新的变量(为NSString类型)并将其设为视图控制器的userName属性。
3)@"World"是一个字符串常量,用NSString的实例表示。如果用户运行应用程序但不输入任何文本(即[nameString length]==0),nameString将包含字符串"World"。
4)initWithFormat:方法是由Foundation框架提供的。作用是按提供的格式字符串所规定的格式创建一个新的字符串(很像ANSI C库中的printf函数)。在格式字符串中,%@充当字符串对象的占位符。此格式字符串的双引号中的所有其他字符都将如实显示在屏幕上。
2.6.3 把视图控制器作为输入文本框的委托
如果生成并运行应用程序,在单击按钮时应该会看到标签显示“Hello,World!”。如果你选择文本栏并开始在键盘上键入,会发现完成文本输入后,仍然无法让键盘消失。
在iOS应用程序中,允许文本输入的元素成为第一响应器时,键盘会自动出现;元素失去第一响应器状态时,键盘会自动消失。(前面提到过第一响应器是第一个接收各种事件通知的对象,例如轻按文本栏来调出键盘。)虽然无法从应用程序直接将消息发送给键盘,但是可以通过切换文本输入UI元素的第一响应器状态这种间接方式,使键盘出现或消失。
UITextFieldDelegate协议是由UIKit框架定义的,它包括textFieldShouldReturn:方法,当用户轻按“Return”按钮(不管该按钮的实际名称是什么)时,文本栏调用该方法。因为已经将视图控制器设定为文本栏的委托,可以实施该方法,通过发送resignFirstResponder消息强制文本栏失去第一响应器状态,利用该方法的副作用使键盘消失。
注意 协议基本上只是一个方法列表。如果一个类符合(或采用)某个协议,则需要保证它可以实施该协议所要求的方法(协议也可以包括一些可选的方法)。委托协议指定了一个对象可能向其委托发送的所有消息。
将HelloWorldViewController配置为文本栏的委托的步骤如下。
步骤1 如有需要,在项目导航器中选择HelloWorldViewController.m。
步骤2 实施textFieldShouldReturn:方法。
此方法应该指示文本栏放弃第一响应器的状态。实现结果是:
- (BOOL)textFieldShouldReturn:(UITextField *)theTextField { if (theTextField == self.textField) { [theTextField resignFirstResponder]; } return YES; }
在本应用程序中,没有必要真正测试theTextField==self.textField表达式,因为只有一个文本栏。但这是一个很好的模式,因为有些场合你的对象可能不只是一个同类对象的委托,所以可能需要对它们加以区分。
步骤3 在项目导航器中选择HelloWorldViewController.h。在@interface行的末尾添加<UITextFieldDelegate>。接口声明应如下:
@interface HelloWorldViewController :UIViewController <UITextFieldDelegate>
此声明指定HelloWorldViewController类采用UITextFieldDelegate协议。
2.7 测试应用程序
生成并运行应用程序。现在,一切的表现都应该如你所期望的那样。在Simulator中,输入你的姓名后,单击“Done”按钮使键盘消失,然后单击“Hello”按钮将在标签中显示“Hello,你的姓名!”。如果应用程序的表现不是你所期望的,需要进行故障排除。
2.7.1 排查和检测代码
如果应用程序未能正确工作,可尝试本章讲述的解决问题方法。如果应用程序仍然不能正确工作,可将你的代码与本章末尾给出的清单进行比较。
1.查看代码编译器警告
代码编译时应该不会有任何警告。如果真的收到警告,就很有可能是代码出错了。因为Objective-C是一种非常灵活的程序设计语言,有时候编译器给出的也仅仅是一些警告而已。
2.检查Storyboard文件
如果程序未能正确工作,开发者会很自然地去检查源代码来找出错误。但使用Cocoa Touch又增添了另一个层面的问题:应用程序的大部分配置可能是“编码”在串联图中。例如,如果连接不正确,应用程序的行为就会与你的期望不符。
如果单击按钮时文本没有更新,可能是没有将按钮的操作连接到视图控制器,或是没有将视图控制器的Outlet连接到文本栏或标签。
如果单击“Done”按钮时键盘不消失,可能是没有将文本栏的委托连接好,或者把视图控制器的textField Outlet连接到了文本栏。务必在串联图上检查文本栏的连接,按住Ctrl键单击文本栏以显示半透明的连接面板。在delegate outlet和textField引用Outlet的旁边应该看到圆圈。
3.检查委托方法命名
与委托有关的一个常见错误是拼错委托方法的名称。即使已经正确设定了委托对象,但是如果委托未在其方法实现中使用正确的名称,则不会调用正确的方法。通常最好的做法是从文稿中复制和粘贴委托方法声明(例如textFieldShouldReturn:)。
2.7.2 程序代码清单
本节提供HelloWorldViewController类的接口和实现文件的代码清单。注意,该代码清单并未列出Xcode模板提供的注释和其他方法的实现。
代码清单2-1 接口文件HelloWorldViewController.h
--------------------------------------------------------------------------------
#import <UIKit/UIKit.h> @interface HelloWorldViewController :UIViewController <UITextFieldDelegate> @property (copy, nonatomic) NSString *userName; @end
--------------------------------------------------------------------------------
代码清单2-2 实现文件HelloWorldViewController.m
--------------------------------------------------------------------------------
#import "HelloWorldViewController.h" @interface HelloWorldViewController () @property (weak, nonatomic) IBOutlet UITextField *textField; @property (weak, nonatomic) IBOutlet UILabel *label; - (IBAction)changeGreeting:(id)sender; @end @implementation HelloWorldViewController - (void)viewDidLoad{ [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{ return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown); } - (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; } - (BOOL)textFieldShouldReturn:(UITextField *)theTextField { if (theTextField == self.textField) { [theTextField resignFirstResponder]; } return YES; } @end
--------------------------------------------------------------------------------
2.8 小结
通过本章的讲解,你一定会对开发iPhone应用程序有所了解,进而对基础开发过程有初步了解。但是要开发出优秀的iPhone应用程序,还有很多的知识需要掌握和学习,后面将会逐步介绍。