iOS软件开发兵法
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第2章 让舞台多姿多彩:设计第一个iPhone界面

在本章中将学习使用 Xcode,内容包括介绍 Xcode 基础知识,了解视图管理中心Interface Builder,创建视图控制器,以及视图的旋转、按钮、文本、表格视图、导航控制器、标签控制器等。

2.1 · 什么是Xcode

Xcode前身是NeXT的Project Builder,从Xcode 3.1开始附带iOS SDK,作为iOS的开发环境,Xcode是开发人员的不二选择。在编写此书之时,Xcode的最新版本是Xcode 4.3,使用者可以从 Mac App Store 免费下载,下载地址为:http://itunes.apple.com/us/app/xcode/id497799835?ls=1&mt=12。

Xcode欢迎界面如图2-1所示。

图2-1 Xcode欢迎界面

欢迎界面左侧的区域有4个选项,分别为:

Create a new Xcode project,创建一个新的Xcode项目。

Connect to a repository,连接到一个代码库。

Learn about using Xcode,学习如何使用Xcode,将会转到Organizer文档中心的“关于Xcode”部分。

Go to Apple's developer portal,转到苹果开发者门户。

欢迎界面右侧显示最近更新的项目列表,按最近打开时间倒序排列。

欢迎界面下侧的“Show this window when Xcode launches”项表示当勾选此项时,每次打开Xcode 都将显示本窗口,如果不希望再见到它,则可以取消勾选。

2.2 · 创建项目

创建一个新项目,点击欢迎界面中的“Create a new Xcode project”,进入选择项目模板界面,如图2-2所示,项目模板可以帮助快速创建某类项目需要的基本控件与类。

图2-2 创建新项目

在这个界面中可以看到模板分为两大类:iOS与Mac OS X。因为是学习iPhone与iPad开发,所以只需要关注iOS项目模板即可。

iOS模板分为3小类:Application模板、Framework & Library(框架与库)及Other(其他模板)。一般开发中比较常用的是Application模板,下面着重介绍Application相关模板适用的应用类型。

Master-Detail Application,主从应用,会生成一个根视图与子视图,根视图中包含一个表格视图。

OpenGL Game,基于OpenGL的游戏应用。

Page-Based Application,翻页应用,自带翻页效果,适合制作电子图书。

Single View Application,单一视图应用,会创建一个空视图为根视图。

Tabbed Application,标签应用,会创建两个视图,可以通过标签控制器进行视图切换。

Utility Application,会创建两个视图,视图之间可以翻转切换。

Empty Application,空应用,不会生成视图。

在这里选择“Single View Application”(单一视图应用),点击“Next”按钮进入下一步,如图2-3所示。

图2-3 填写项目选项

在这一步需要设置项目名称(Product Name),在这里将项目命名为“HelloWorld”。还有公司标识(Company Identifier)、包标识(Bundle Identifier),包标识是不可修改的,它由公司标识、项目名称组成。下面是选择设备类型(Device Family),可以选择iPhone、iPad或Universal(全部),Universal表示项目兼容iPhone和iPad。

勾选“Use Storyboard”项表示使用Storyboard视图,它能将多个视图文件像连环画一样串联起来,减少了视图切换的代码量。StoryBoard是iOS 5的新特征,意在代替历史悠久的NIB视图文件。目前还不需要使用它,所以在这里不勾选此项。

勾选“Use Automatic Reference Counting”(ARC)项表示使用自动引用计数。自动计数是一种在编译期间工作,能够帮助管理内存的技术,通过它开发人员不需要在内存的保持(retain)、释放等方面花费精力。ARC在编译期间为每个Objective-C指针变量添加合适的retain、release、autorelease等函数,保证每个变量的生存周期控制在合理的范围内,以实现代码上的自动内存管理。ARC技术是随着Xcode 4.2一起发布的,在默认工程模板中,可以指定工程是否支持ARC技术,如果不指定工程支持ARC技术,在代码中必须使用管理内存的代码来管理内存。ARC支持iOS 4与iOS 5,如果确信应用程序不会运行在低于iOS 4系统版本的设备上,则可以勾选此项。

勾选“Include Unit Tests”项表示包含单元测试,单元测试可以对个别单元的源代码进行测试,以确定它们是否适合使用,在这里不勾选此项。

完成这些选项设置之后,点击“Next”按钮进入下一步,如图2-4所示。

图2-4 选择项目存放路径

在这一步可以选择项目的存放路径,建议将相关项目都存放在一个独立的目录中,以方便管理。

“Create local git repository for this project”项是Xcode 4之后版本增加的功能,支持Git版本控制系统,勾选此项以后Xcode可以创建一个本地的Git版本库,请勾选此项。

点击“Create”按钮,一个项目就创建完成了。创建完成的项目如图2-5所示。

图2-5 创建完成的项目

2.3 · Xcode项目窗口

下面介绍Xcode 的项目窗口。项目窗口分为5大部分:工具栏(Toolbar)、导航区域(Navigator area)、编辑区域(Editor area)、调试区域(Debug area)与检查窗格(Inspector pane)。

2.3.1 工具栏(Toolbar)

项目窗口顶部是工具栏,提供了很多命令,包括运行程序按钮(Run)、停止运行按钮(Stop),停止运行按钮旁边的下拉项可以选择程序运行环境,可以是iPhone/iPad模拟器,也可以是真机。

Breakpoints按钮表示是否执行断点测试,断点测试可以让程序中断在需要的地方,从而方便其分析。也可以在一次调试中设置断点,只需要让程序自动运行到设置断点位置,便可在此位置产生中断,极大地方便了操作,同时节省了时间。断点测试的内容本章只需了解即可。

工具栏的正中位置是项目运行状态,如果程序有警告或异常,则都会在此显示。

工具栏右侧有3类按钮:Editor、View和Organizer。

1.Editor按钮

可以切换编辑器的3种编辑模式:标准编辑模式、助手编辑模式和版本编辑模式。

(1)标准编辑模式(Standard Editor)。

常规的一个编辑窗格,如图2-6所示。

图2-6 标准编辑模式

(2)助手编辑模式(Assistant Editor)。

两个编辑窗口,如选中.m文件,另一个窗口将显示.h接口文件,如图2-7所示。

图2-7 助手编辑模式

(3)版本编辑模式(Version Editor)。

两个编辑窗口,能将上次保存的内容与当前内容进行比较显示,如图2-8所示。

图2-8 版本编辑模式

2.View按钮

窗格显示方式,可以显示或隐藏左侧、下侧、右侧的窗格,所有窗格打开后如图2-9所示。

图2-9 窗格全部显示

3.Organizer按钮

它提供了工程和源代码管理、Action 支持,甚至自带了一个微型的开发程序使用的版本控制工具,帮助管理不同时期的代码改动,这个功能是自Xcode 3.0版本开始提供的,如图2-10所示。

图2-10 Organizer 界面

2.3.2 导航区域(Navigator area)

项目窗口左侧的导航区域默认显示的是文件组与文件列表,项目中的所有资源都在这里分类显示,还包括一系列的项目设置。单击项目左侧的三角形图标可以展开或收起该项目的子项目,如图2-11所示。

图2-11 导航区域

与项目同名的文件夹是最常用的,如果创建了一个HelloWorld项目,那么这个文件夹的名称就是HelloWorld,编写的大多数代码都保存在这里。它还包含一个Supporting Files文件夹,此文件夹下存放着info.plist文件,包含程序相关属性的信息列表;InfoPlist.strings文件用来实现程序的本地化功能;main.m 文件包含应用程序的 main 方法,一般不需要修改它;Prefix.pch是预编译头文件,Xcode将预编译包含在此文件中的头文件,将不常改动的头文件包含在此文件中可以减少编译项目所需要的时间。

Frameworks是一种特殊的库,可以包含代码、图像和声音文件等资源。向这个文件夹中添加的任何框架或库都将链接到程序中,并且代码能使用包含在该框架或库中的对象、方法和资源。项目中已经默认链接了最常用的框架和库,目前不需要操作这个文件夹。

Products 包含了项目在编译后生成的应用程序,展开这个文件夹,可以看到一个名为HelloWorld.app的文件,它是这个项目的唯一产品。因为还未编译项目,当前这个文件显示为红色,表示无法找到此文件,这是Xcode无法找到物理文件时的提示方法。

文件组与文件列表介绍完之后,通过点击左侧窗格中的选项卡菜单,可以切换到查找、错误与警告、程序线程、断点、程序消息等功能窗格,这些功能当前只需要了解即可,本书4.4.1节“项目文件介绍”中有更详细的描述。

2.3.3 编辑区域与调试区域(Editor area/Debug area)

中间窗格为编辑区域与调式区域,上部为编辑区域,在这里可以编写代码与设计视图;下部为调试区域,调试区域左侧为调试时变量显示区域,右侧为程序输出区域,如图2-12所示。

图2-12 编辑区域与调试区域

2.3.4 检查窗格(Inspector pane)

右侧的检查窗格分为上下两个部分,上部为类或视图的属性和设置,下部为文件模板库、代码库、控件库和媒体库,如图2-13所示。

图2-13 检查窗格

了解了Xcode的项目窗口之后,在下一节中将介绍Xcode内置的图形用户界面管理工具Interface Builder。

2.4 · Interface Builder

Interface Builder(简称 IB)是 Xcode 套件的一部分。iPhone/iPad 开发者可以使用Interface Builder来创建和修改应用程序的图形用户界面,其数据以XML的形式存储在XIB文件中。

在Xcode 4之后,Interface Builder 和Xcode整合在一起,即在Xcode中直接提供编辑图形界面的功能,Interface Builder不再作为单独的程序。

点击HelloWorldViewController.xib文件,可以看到Interface Builder界面如图2-14所示。

图2-14 Interface Builder界面

左侧面板的Placeholders区域有两个图标:

File's Owner,表示从磁盘加载NIB文件的对象,可以认为File's Owner是“拥有”此NIB文件的对象。

First Responder,表示用户正与之交互的对象,First Responder将随着用户与界面的交互而变化。例如,用户正在文本字段中输入数据,那么文本字段就是当前的 First Responder。如果还有疑问,请不必担心,本书将在后面章节中介绍这方面内容。

Objects 区域显示的是 NIB 文件中创建的对象实例,现在例子中只有一个空视图对象View。

2.5 · Hello World经典程序

这一节将完成经典的Hello World程序。首先点击顶部工具栏右侧的View按钮,展开右侧窗格,将控件库中的Label控件用鼠标拖动到视图中,如图2-15所示。然后选中Label,双击可以修改Label文本,将文本内容改为“Hello World”,再使用鼠标将Label拖动到视图正中位置。不用担心不能拖放到准确位置,当拖动时会发现有蓝色的辅助虚线,借助它可以很容易地将Label拖放到水平居中对齐的位置上。

图2-15 将Label组件拖动到视图中

现在运行程序,在运行之前,需要选择程序运行环境,如图2-16所示。

图2-16 选择程序运行环境

点击“Run”按钮或按下快捷键“+R”运行程序,程序运行结果如图2-17所示。

图2-17 运行模拟器,程序运行结果

到目前为止,还未写一行代码,是不是很简单?欣赏完自己的作品之后,退出iOS模拟器。Xcode与模拟器是单独的应用程序,退出iOS模拟器并不会退出Xcode,再次运行应用程序时,iOS模拟器还会启动。

2.6 · 实现视图自动旋转功能

iPhone/iPad支持纵向和横向两种显示模式,当旋转这些设备时,能改变应用程序的方向,这种行为称为自动旋转,iPhone/iPad自带的Safari程序就是一个很好的例子,如图2-18所示。

图2-18 iPhone/iPad支持纵向和横向两种显示模式

并不是所有的应用程序都需要支持自动旋转,例如观看影片适合横向模式,通讯录更适合纵向模式。在开发中使用自动旋转的基本原则是:当自动旋转可以增强用户体验时,才将它添加到应用程序中。

创建一个新项目,打开Xcode→Create a new Xcode project→选择Application模板“Single View Application”→Next→填写项目名称:Autorotate→Next→选择项目保存位置→Create。

然后打开AutorotateViewController.xib文件,往视图中拖入4个按钮,依次双击按钮,在按钮中输入数字1、2、3、4,如图2-19所示。

图2-19 将按钮组件拖动到视图中

接下来打开AutorotateViewController.m文件,找到shouldAutorotateToInterfaceOrientation:方法,此方法应该和下面显示的内容一致。

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}

系统通过调用这个方法来询问视图控制器是否应该旋转到指定的方向,共定义了4个方向,分别对应着手持iPhone/iPad的4种方式。

· UIInterfaceOrientationPortrait,设备向上,Home键在底部。

· UIInterfaceOrientationPortraitUpsideDown,设备向下,Home键在顶部。

· UIInterfaceOrientationLandscapeLeft,设备朝左,Home键在右侧。

· UIInterfaceOrientationLandscapeRight,设备朝右,Home键在左侧。

当设备的方向发生改变时,系统将调用这个方法来操作视图控制器,interfaceOrientation参数的取值为上面4个值之一。此方法需要返回YES或NO,以指示是否旋转应用程序的窗口以匹配新的方向。

当前设置为return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown),此方法的默认实现会检查interfaceOrientation,当它与UIInterfaceOrientationPortraitUpsideDown不相等时才返回YES,这样会将应用程序限制为设备向下时(Home键在顶部)不会自动旋转。如果需要启动所有方向都支持自动旋转,则将此行修改为return YES。现在点击“Run”按钮或按下快捷键“+R”运行程序,然后使用快捷键“+左方向键或右方向键”改变模拟器方向,如图2-20和图2-21所示。

图2-20 模拟器纵向显示

图2-21 模拟器横向显示

你会发现横屏之后的显示效果不是很理想,按钮3与按钮4不能显示,按钮2的位置也不正确,这就需要使用自动调整属性功能。

退出模拟器,打开AutorotateViewController.xib文件,选中按钮2,然后打开右侧窗格的大小检查器(Autosizing)面板,如图2-22所示。

图2-22 大小检查器面板

大小检查器用于设置对象的自动调整属性,可以看到一个正方形内部有4个方向的箭头,表示选中对象内部的水平和垂直空间。外部四周有 4 个红色的“I”,表示选中对象边与包含它的视图同侧边之间的距离,如果“I”是虚线,则表示距离是可变的;如果“I”是实线,则间距是不可变的。

去掉左侧的实线“I”,点击一下右侧的虚线使其变成实线,如图2-23所示。

图2-23 设置按钮2右上角侧边距固定属性

依次修改按钮3与按钮4,如图2-24和图2-25所示。

图2-24 设置按钮3左下角侧边距固定属性

图2-25 设置按钮4右下角侧边距固定属性

按钮 1 默认就是右侧和下方间距可变,所以不用修改。现在再一次点击“Run”按钮或按下快捷键“+R”运行程序,横向显示的效果如图2-26所示。

图2-26 运行模拟器结果

现在,当模拟器改变方向时,所有按钮都可以显示出来,它们会调整自已的位置,以适应方向的改变。

还有两种方式可以实现屏幕旋转时的界面调整:在旋转时重新构建视图和使用两个视图进行切换。这些内容将在以后进行详细介绍。

2.7 · 掌握更多的常用控件

本节要做一个稍微复杂点的例子,使用常见的控件如Lable、Round Rect Button、Text Field、Switch与Image View 做一个用户登录界面,在这个应用中将介绍这些常用控件的使用方法。

新建一个项目,打开Xcode→Create a new Xcode project→选择Application模板“Single View Application”→Next→填写项目名称:ViewObjects→Next→选择项目保存位置→Create。

打开ViewObjectsViewController.xib文件,将一个Image View拖入视图中,并调整它的大小,如图2-27所示。

图2-27 将Image View拖动到视图中

然后将一张图片添加到项目中,添加图片/资源的方法是选中文件夹并单击右键,选择“Add Files to‘ViewObjects’”,这个操作可以选择一个或多个图片/资源,如图2-28所示。

图2-28 添加图片/资源

特别需要注意的是,一定要勾选“Copy items into destination group's folder (if needed)”项,勾选此项后文件将会复制到项目目录下。选择好图片之后,点击“Add”按钮,图片文件就被加入到项目中了。

为了方便管理,在ViewObjects目录下创建了一个名为“Images”的子目录,专门存放图片文件,如图2-29所示。

图2-29 将图片文件放到Images目录下以方便管理

再次打开ViewObjectsViewController.xib文件,选择刚刚拖入到视图中的Image View,打开属性面板,在Image属性下拉项中选择刚刚导入到项目中的图片文件apple.png,如图2-30所示。

图2-30 Image View 对象与图片资源链接

在Interface Builder中调整Image View到合适大小,或者在对象尺寸面板中输入精确值,如图2-31所示。

图2-31 在对象尺寸面板中调整Image View大小

调整大小完毕后,点击“Run”按钮或按下快捷键“+R”运行程序,会发现屏幕中上方显示一个白底的图片,不是很美观。返回Xcode选中Image View,将Image View的背景颜色由默认的灰色修改为白色,重新点击“Run”按钮或按下快捷键“+R”运行程序,效果如图2-32所示。

图2-32 Image View在模拟器中显示的效果

这一步需要在视图中添加用户名与密码输入框。首先拖入两个Text Field控件,并调整大小,然后在每个Text Field控件前各加入一个Label,将Label的内容修改为“昵称”与“密码”,再根据辅助线将它们调整到合适的位置,如图2-33所示。

图2-33 在主视图中添加Label组件

Text Field文本字段是最常用与最复杂的控件之一,当选中文本字段时,它会从屏幕下方弹出输入键盘,可以在属性面板里设置不同的键盘显示方式,并且针对不同的文本字段类型调整它的属性,如昵称只需要输入纯文本,而密码则需要隐藏显示。选中“密码”文本框后,在属性面板中勾选“Secure”项,现在点击“Run”按钮或按下快捷键“+R”运行程序,然后点击“密码”文本框输入密码时就不再以明文显示了,如图2-34所示。

图2-34 “密码”文本框键盘

视图创建完成后,打开ViewObjectsViewController.h接口文件,需要给两个文本字段定义两个输出口,代码如下:

@interface ViewObjectsViewController : UIViewController
{
    IBOutlet UITextField *userNameField;
    IBOutlet UITextField *passwordField;
}
@property (nonatomic, retain) UITextField *userNameField;
@property (nonatomic, retain) UITextField *passwordField;
@end

然后打开ViewObjectsViewController.m文件,修改代码如下:

#import "ViewObjectsViewController.h"
@implementation ViewObjectsViewController
@synthesize userNameField;
@synthesize passwordField;
……

输出口是用关键字IBOutlet声明的实例变量,它唯一的作用就是告诉Interface Builder这个实例变量将会链接到nib/xib中的控件对象。

再次打开ViewObjectsViewController.xib文件,选择“File's Owner”,打开Connections面板,可以看到刚刚声明的两个输出口,如图2-35所示。

图2-35 在Connections面板中查看输出口

点击输出口后面的圆框,然后按住鼠标左键将输出口拖动到相对应的 Text Field 控件上,就完成了输出口与控件的链接,如图2-36所示。

图2-36 拖动输出口与控件链接

将昵称与密码和输出口链接完成之后,再添加一个功能,就是当输入结束后,点击键盘上的Return键关闭键盘,这需要添加一个方法 textFieldDoneEdition。

修改ViewObjectsViewController.h 接口文件,代码如下:

@interface ViewObjectsViewController : UIViewController
{
    IBOutlet UITextField *userNameField;
    IBOutlet UITextField *passwordField;
}
@property (nonatomic, retain) UITextField *userNameField;
@property (nonatomic, retain) UITextField *passwordField;
// 关闭键盘
- (IBAction) textFieldDoneEdition: (id) sender;
@end

在ViewObjectsViewController.m中添加方法,代码如下:

// 关闭键盘
- (IBAction) textFieldDoneEdition: (id) sender
{
[sender resignFirstResponder];
}

接下来打开 ViewObjectsViewController.xib 文件,选择“昵称”文本框,然后在Connections面板中将“Did End On Exit”事件旁边的圆圈拖动到“First Responder”,将它链接到textFieldDoneEdition:方法;对“密码”文本框也进行同样的操作,如图2-37所示。

图2-37 为键盘上的“Return”键链接事件

现在点击“Run”按钮或按下快捷键“+R”运行程序,点击“昵称”文本框输入一些内容,然后点击Return键,将会关闭键盘。

但在使用键盘其他程序中,在有文本输入的情况下,在视图中任何无活动控件的位置点击一下都可以关闭键盘。应该如何实现这个功能呢?其实非常简单,只需要创建一个不可见的按钮,将它放到所有控件的下面即可,当点击它时就会关闭键盘。

打开ViewObjectsViewController.xib文件,从控件库中将一个Round Rect Button拖动到视图中,然后使用按钮边缘的调整点将它填满整个屏幕,并将它拖放到所有控件之下。操作方法是,在左侧的Objects面板中将Button 拖放到View视图最上面的位置,如图2-38所示。然后将按钮样式改为Custom,Custom适用于无外观的按钮,用在这里非常合适。

图2-38 将Button拖放到主视图最上面的位置

接下来编写一个方法closeKeyBoard,代码如下:

@interface ViewObjectsViewController : UIViewController
{
    IBOutlet UITextField *userNameField;
    IBOutlet UITextField *passwordField;
}
@property (nonatomic, retain) UITextField *userNameField;
@property (nonatomic, retain) UITextField *passwordField;
// 关闭键盘
- (IBAction) textFieldDoneEdition: (id) sender;
// 点击背景关闭键盘
- (IBAction) closeKeyBoard: (id) sender;
@end

在ViewObjectsViewController.m中添加相应的方法,代码如下:

// 点击背景关闭键盘
- (IBAction) closeKeyBoard: (id) sender
{
    [userNameField resignFirstResponder];
    [passwordField resignFirstResponder];
}

完成后打开ViewObjectsViewController.xib文件,选中背景按钮,打开Connections面板,将“Touch Up Inside”后的圆圈拖动到“First Responder”与closeKeyBoard:方法链接,如图2-39所示。

图2-39 Button的Touch Up Inside事件绑定closeKeyBoard:方法

保存文件,再次运行应用程序,这一次可以通过两种方式关闭键盘:点击Return键,或者单击没有活动控件的任意区域。

继续完成登录界面,将一个 Switch 开关控件拖入视图中,然后在它的后面摆放一个Label控件,并将文字修改为“记住密码”,如图2-40所示。

图2-40 在主视图中添加Switch 开关控件

默认不应该记住密码,所以选中开关控件,将属性 State 设为 Off。当用户打开此项时,需要给用户一个提示,因此在开关控件下方添加一个文本控件 Label,将内容修改为:一周内自动登录,并将它的显示属性设为隐藏,如图2-41所示。在一般的应用程序中,登录默认都会记住密码,但为了演示开关控件的用法,这里做了这样一个提示功能。

图2-41 在主视图中添加隐藏的文本控件

再回到ViewObjectsViewController.h接口文件中,增加两个输出口,代码如下:

@interface ViewObjectsViewController : UIViewController
{
    IBOutlet UITextField *userNameField;
    IBOutlet UITextField *passwordField;
    IBOutlet UISwitch *remember;
    IBOutlet UILabel *rememberMessage;
}
@property (nonatomic, retain) UITextField *userNameField;
@property (nonatomic, retain) UITextField *passwordField;
@property (nonatomic, retain) UISwitch *remember;
@property (nonatomic, retain) UILabel *rememberMessage;
// 关闭键盘
- (IBAction) textFieldDoneEdition: (id) sender;
// 点击背景关闭键盘
- (IBAction) closeKeyBoard: (id) sender;
@end

修改ViewObjectsViewController.m 实现文件,代码如下:

#import "ViewObjectsViewController.h"
@implementation ViewObjectsViewController
@synthesize userNameField;
@synthesize passwordField;
@synthesize remember;
@synthesize rememberMessage;
……

回到视图界面,将刚才定义的两个输出口与视图中对应的实例对象进行链接,如图2-42所示。

图2-42 将输出口与视图中对应的控件对象进行链接

链接完成后添加一个方法 displayRememberMessage:。

修改ViewObjectsViewController.h接口文件,代码如下:

……
// 关闭键盘
- (IBAction) textFieldDoneEdition: (id) sender;
// 点击背景关闭键盘
- (IBAction) closeKeyBoard: (id) sender;
// 显示/隐藏记住密码提示信息
- (IBAction) displayRememberMessage: (id) sender;
@end

修改ViewObjectsViewController.m 实现文件,代码如下:

……
// 显示/隐藏记住密码提示信息
- (IBAction) displayRememberMessage: (id) sender
{
    UISwitch *rememberSwitch = sender;
    if (rememberSwitch.isOn)
    {
        [rememberMessage setHidden:NO];
    }
    else
    {
        [rememberMessage setHidden:YES];
    }
}

再回到视图界面,选中开关控件并打开 Connections 面板,将开关控件的“Value Changed”事件后的圆圈拖到“First Responder”,与 displayRememberMessage:方法链接。“Value Changed”表示当实例对象的值被改变时触发事件。

现在点击“Run”按钮或按下快捷键“+R”运行程序,当将开关按钮选定为ON时,将会显示提示信息;重新将开关按钮选定为OFF,提示信息消失,效果如图2-43所示。

图2-43 模拟器运行效果

最后再添加一个提交按钮,放置在登录界面的下方,如图2-44所示。点击“登录”按钮,将会进行验证,检查“昵称”或“密码”是否已经填写完成,如果全部填写正确就提示登录成功。当然,在正常情况下,单击按钮提交后应该是经过服务器端验证后才会给出是否登录成功提示,本书的内容因为不涉及服务器端相关内容,所以在这里只做一个简单的提示功能。

图2-44 在主视图中添加“登录”按钮

下一步添加方法 login:。

修改ViewObjectsViewController.h接口文件,代码如下:

// 关闭键盘
- (IBAction) textFieldDoneEdition: (id) sender;
// 点击背景关闭键盘
- (IBAction) closeKeyBoard: (id) sender;
// 显示/隐藏记住密码提示信息
- (IBAction) displayRememberMessage: (id) sender;
// 登录
- (IBAction) login : (id)sender;
@end

修改ViewObjectsViewController.m 实现文件,代码如下:

……
// 登录
- (IBAction) login : (id)sender
{
    NSString *message = @"";
    if ([userNameField.text length] < 1)
    {
        message = @"请填写用户名";
    }
    else if ([passwordField.text length] < 6)
    {
        message = @"密码最少为6位";
    }
    else
    {
        message = @"登录成功!";
    }
    UIAlertView *al = [[UIAlertView alloc]
                        initWithTitle:@"提示信息"
                        message:message
                        delegate:self
                        cancelButtonTitle:@"确定"
                        otherButtonTitles:nil
                        ];
    [al show];
}

根据输入的昵称与密码的长度来设置不同的消息。UIAlertView构建了一个简单的提示框,initWithTitle 参数设置了提示框的标题;message参数设置了提示框的消息内容;delegate参数一般不用修改,设为 self;cancelButtonTitle 参数设置了退出按钮的标题;otherButtonTitles参数可以额外地添加多个按钮。

返回视图界面,选中“登录”按钮,按住Control键将它拖到“First Responder”,与login:方法链接。

注意:这里链接UIAction的方法与之前不同,使用这种方法相当于给实例对象的“Touch Up Inside”事件链接了方法。

最后点击“Run”按钮或按下快捷键“+R”运行程序,点击“登录”按钮,如果没有填写昵称或用户名,将会进行相应的提示;如果全部填写正确,就会提示登录成功,如图2-45所示。

图2-45 模拟器运行效果

至此,一个简单的登录界面就完成了。下一节将介绍一个十分常用的控件:表格视图。

2.8 · 掌握表格视图

表格视图是用于向用户显示数据的一种最常见方式,例如iPhone 自带的电子邮件应用程序、联系人通讯录等都是表格视图。表用于显示数据列表,数据列表中的每一项都由行表示,每一行只有一列,表格视图并没有限制行的数量,表格视图行的数量受设备可用存储空间的限制。

表格视图分为两种基本样式:索引表和分组表。

(1)索引表。索引表又称为无格式表,它是默认的样式,所有没有圆角矩形属性的表都是索引表。

(2)分组表。分组表中的每个组都由包含在圆角矩形中的多行组成。

下面就实现一个简单的邮件列表界面,在这个例子中主要使用的就是表格视图。打开Xcode→Create a new Xcode project→选择Application模板“Single View Application”→Next→填写项目名称:TableView→Next→选择项目保存位置→Create。

创建好项目之后,选择 TableViewViewController.xib 文件打开Interface Builder,往视图中拖入一个TableView控件,并调整TableView大小,直到占满整个视图,如图2-46所示。

图2-46 在主视图中添加TableView控件

点击“Run”按钮或按下快捷键“+R”运行程序,可以看到一个空的索引表,如图2-47所示。

图2-47 模拟器运行效果

如何让表格显示数据呢?这就需要给视图链接数据源(dataSource)和数据委托(delegate)了。打开视图文件,选中TableView并打开Connections面板,将dataSource与delegate后面的圆圈拖到“File's Owner”图标上,如图2-48所示。

图2-48 TableView链接数据源与数据委托

现在修改TableViewViewController.h接口文件,代码如下:

@interface TableViewViewController : UIViewController
<UITabBarDelegate, UITableViewDataSource>
{
    NSArray *tableIndexData;
}
@property (nonatomic, retain) NSArray *tableIndexData;
@end

接下来修改TableViewViewController.m文件,代码如下:

#import "TableViewViewController.h"
@implementation TableViewViewController
@synthesize tableIndexData;
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
    self.tableIndexData = [[NSArray alloc] initWithObjects:@"收件箱", @"草稿", @"已发送",
                            @"废纸篓", nil];
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation
            == UIInterfaceOrientationPortraitUpsideDown);
}
#pragma mark - Table View Methods
// 表格数据行数
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.tableIndexData count];
}
// 表格数据
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath
    *)indexPath
{
    static NSString *tableIdentifier = @"tableIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:tableIdentifier];
    if(cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithFrame:CGRectZero];
    }
        cell.textLabel.text = [self.tableIndexData objectAtIndex:[indexPath row]];
    return cell;
}
@end

在 viewDidLoad 中定义了一个数组,用它来作为表格数据,并添加了两个方法tableView:numberOfRowsInSection: 和tableView:cellForRowAtIndexPath:。

· 方法tableView:numberOfRowsInSection:,用来指定表格的分组有多少行,默认的数量是1。

· 方法tableView:cellForRowAtIndexPath:,当表格视图需要绘制其中一行时,就会调用这个方法。在方法中使用static NSString *tableIdentifier = @"tableIdentifier"; 声明了一个静态字符串实例,这个字符串表示某种表单元的键,在当前表格中只使用一种单元,因此只需要定义一种标志符。表格中的每一行都是由一个 UITableViewCell实例表示的,这个实例是UIView的子类,这就意味着每一行都可以有子视图,这样就可以使用表格视图制作出图文混排的列表。

当一个表格视图单元滚出屏幕时,另一个表格视图单元就会从底部滚动到屏幕上。所以使用之前声明的字符串标志符,定义一个可重用的表格视图单元:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:tableIdentifier];

下一步检查cell是否为空(nil),如果为空,则用上面所声明的标志符手动创建一个新的表格视图单元:

cell = [[UITableViewCell alloc] initWithFrame:CGRectZero];

最后通过[indexPath row]获取显示行的行号,并从数组中取出相应的值显示到单元格之中:

cell.textLabel.text = [self.tableIndexData objectAtIndex:[indexPath row]];

点击“Run”按钮或按下快捷键“+R”运行程序,效果如图2-49所示。

图2-49 模拟器运行效果

如果可以向每行添加一个图片就更好了。实际上,在每个单元格前添加一个图片非常简单,只需添加两行代码就行。首先在随书附带的示例文件中找到 start.png 文件,然后修改 tableView: cellForRowAtIndexPath:方法。代码如下:

- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath
  *)indexPath
{
    static NSString *tableIdentifier = @"tableIdentifier";
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:tableIdentifier];
        if(cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithFrame:CGRectZero];
    }
        cell.textLabel.text = [self.tableIndexData objectAtIndex:[indexPath row]];
    UIImage *image = [UIImage imageNamed:@"email.png"];
    cell.imageView.image = image;
    return cell;
}

再次点击“Run”按钮或按下快捷键“+R”运行程序,效果如图2-50所示。

图2-50 模拟器运行效果

至此,一个最简单的索引表例子:邮件界面基本就完成了。下面在此基础上来做一个分组表的例子。首先在项目中添加一个控制器,然后选中项目目录,单击右键选择“New File...”,如图2-51所示。

图2-51 添加新控制器

选择“UIViewController subclass”表示继承自UIView,点击“Next”按钮进入下一步,如图2-52所示。

图2-52 选择“UIViewController subclass”

下一步不用修改,直接点击“Next”按钮,如图2-53所示。

图2-53 控制器继承自UIViewController

注意:选择“With XIB for user interface”才会同时生成一个NIB文件。

最后输入控制器名称为 EmailViewController,然后点击“Save”按钮保存,如图2-54所示。

图2-54 设置控制器名称与存放路径

控制器创建完成之后,需要修改 TableViewViewController,点击表格单元格可以转到新建的控制器。因为涉及多视图操作,需要修改 AppDelegate,定义一个导航控制器(UINavigationController,导航控制器的详细内容将在下节中进行详细介绍)。

修改TableViewAppDelegate.h接口文件,代码如下:

@interface TableViewAppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UINavigationController *navigationController;
@end

修改TableViewAppDelegate.m实现文件,代码如下:

#import "TableViewAppDelegate.h"
#import "TableViewViewController.h"
@implementation TableViewAppDelegate
@synthesize window = _window;
@synthesize navigationController = _navigationController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary
  *)launchOptions
{
    // Override point for customization after application launch.
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    TableViewViewController *controller = [[TableViewViewController alloc] initWithNibName:
    @"TableViewViewController" bundle:nil];
    self.navigationController = [[UINavigationController alloc] initWithRootViewController:
    controller];
    self.window.rootViewController = self.navigationController;
    [self.window makeKeyAndVisible];
    return YES;
}

给表格单元格添加一个指示标记,表明单元格可点击。

修改TableViewViewController.m实现文件,代码如下:

#import "TableViewViewController.h"
#import "EmailViewController.h"
@implementation TableViewViewController
@synthesize tableIndexData;
......
#pragma mark - Table View Methods
// 表格数据行数
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.tableIndexData count];
}
// 表格数据
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath
  *)indexPath
{
    static NSString *tableIdentifier = @"tableIdentifier";
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:tableIdentifier];
        if(cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithFrame:CGRectZero];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }
        cell.textLabel.text = [self.tableIndexData objectAtIndex:[indexPath row]];
        UIImage *image = [UIImage imageNamed:@"email.png"];
    cell.imageView.image = image;
    return cell;
}
- (void) tableView : (UITableView *) aTableView didSelectRowAtIndexPath : (NSIndexPath *)
indexPath
{
    EmailViewController *emailViewController = [[EmailViewController alloc] initWithNibName:
    @"EmailViewController" bundle:nil];
    [self.navigationController pushViewController:emailViewController animated:YES];
}
@end

tableView:didSelectRowAtIndexPath:这个方法的作用是,当用户选中某行时,这个回调方法被触发,在这个回调方法中使用导航控制器[self.navigationController pushViewController:emailViewController animated:YES];将视图转换为EmailViewController。

现在点击“Run”按钮或按下快捷键“+R”运行程序,点击表格某一行后会转到EmailViewController 视图,如图2-55所示。

图2-55 模拟器运行效果

现在视图中还没有内容,下一步在这个视图中加入内容,制作一个分组表。

首先打开EmailViewController.xib视图文件,往视图中拖入一个表格控件,然后调整表格,使它占满整个屏幕。然后选中表格视图,将表格视图样式修改为分组模式,如图2-56所示。

图2-56 在主视图中添加Table View控件,控件类型为Grouped

接下来选中Table View并打开Connections 面板,将dataSource与delegate后面的圆圈拖到“File's Owner”图标上,如果忘记这一步视图中将不显示数据。

修改EmailViewController.h接口文件,代码如下:

@interface EmailViewController : UIViewController
<UITabBarDelegate, UITableViewDataSource>
{
    NSDictionary *tableData;
    NSArray *keys;
}
@property (nonatomic, retain) NSDictionary *tableData;
@property (nonatomic, retain) NSArray *keys;
@end

修改 EmailViewController.m 实现文件,代码如下:

#import "EmailViewController.h"
@implementation EmailViewController
@synthesize tableData;
@synthesize keys;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}
- (void)didReceiveMemoryWarning
{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
    // 表格数据
    NSArray *a = [[NSArray alloc] initWithObjects:@"a邮件1", @"a邮件2", @"a邮件3",
    @"a邮件4", nil];
    NSArray *b = [[NSArray alloc] initWithObjects:@"b邮件1", @"b邮件2", @"b邮件3",
    @"b邮件4", nil];
    NSArray *c = [[NSArray alloc] initWithObjects:@"c邮件1", @"c邮件2", @"c邮件3",
    @"c邮件4", nil];
    NSArray *d = [[NSArray alloc] initWithObjects:@"d邮件1", @"d邮件2", @"d邮件3",
    @"d邮件4", nil];
    NSArray *e = [[NSArray alloc] initWithObjects:@"e邮件1", @"e邮件2", @"e邮件3",
    @"e邮件4", nil];
    NSArray *f = [[NSArray alloc] initWithObjects:@"f邮件1", @"f邮件2", @"f邮件3",
    @"f邮件4", nil];
    NSArray *g = [[NSArray alloc] initWithObjects:@"g邮件1", @"g邮件2", @"g邮件3",
    @"g邮件4", nil];
    self.tableData = [NSDictionary dictionaryWithObjectsAndKeys:a, @"a", b, @"b", c,
    @"c", d, @"d", e, @"e", f, @"f", g, @"g", nil];
    // 数据表索引数组,并进行排序
    self.keys = [[self.tableData allKeys] sortedArrayUsingSelector:@selector(compare:)];
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
}
- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark - Table View Methods
// 表格分组数量
- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
{
    return [self.keys count];
}
// 表格数据行数
- (NSInteger) tableView : (UITableView *) tableView numberOfRowsInSection : (NSInteger) section
{
    NSString *key = [self.keys objectAtIndex:section];
    NSArray *dataArray = [self.tableData objectForKey:key];
    return [dataArray count];
}
// 表格数据
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath
    *)indexPath
{
    NSString *key = [self.keys objectAtIndex:[indexPath section]];
    NSArray *dataArray = [self.tableData objectForKey:key];
    static NSString *tableIdentifier = @"tableIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:tableIdentifier];
    if(cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithFrame:CGRectZero];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }
    cell.textLabel.text = [dataArray objectAtIndex:[indexPath row]];
    return cell;
}
// 表格组头部标题
-(NSString*)tableView:(UITableView*)tableView titleForHeaderInSection:(NSInteger)section
{
    NSString *key = [self.keys objectAtIndex:section];
    return key;
}
// 表格索引
- (NSArray *) sectionIndexTitlesForTableView:(UITableView *)tableView
{
    return self.keys;
}
@end

首先在 viewDidLoad 方法中定义一个字典作为表格数据,并将字典的索引存入数组keys,使用[[self.tableData allKeys] sortedArrayUsingSelector:@selector(compare:)];进行排序。

- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
{
    return [self.keys count];
}

numberOfSectionsInTableView:方法的作用是获取分组的数量,只需计算keys 数组的成员数量即可。

- (NSInteger) tableView : (UITableView *) tableView numberOfRowsInSection : (NSInteger)
  section
{
    NSString *key = [self.keys objectAtIndex:section];
    NSArray *dataArray = [self.tableData objectForKey:key];
    return [dataArray count];
}

tableView:numberOfRowsInSection:方法与之前的索引表有所不同,只需根据section 参数获取对应的数据分组,然后统计分组数据的成员数量。

- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath
  *)indexPath
{
    NSString *key = [self.keys objectAtIndex:[indexPath section]];
    NSArray *dataArray = [self.tableData objectForKey:key];
    static NSString *tableIdentifier = @"tableIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:tableIdentifier];
    if(cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithFrame:CGRectZero];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }
    cell.textLabel.text = [dataArray objectAtIndex:[indexPath row]];
    return cell;
}

tableView:cellForRowAtIndexPath: 方法几乎没有太大修改,只是先从字典中获取当前组的数据,然后根据 [indexPath row]将数据写入当前行。

- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    NSString *key = [self.keys objectAtIndex:section];
    return key;
}

TableView:titleForHeaderInSection:方法用于设置表格组头部标题。

- (NSArray *) sectionIndexTitlesForTableView:(UITableView *)tableView
{
    return self.keys;
}

sectionIndexTitlesForTableView:方法用于添加表格索引,只需要返回keys数组便可。

设置完成之后点击“Run”按钮或按下快捷键“+R”运行程序,可以看到数据已经分组显示,点击左侧的表格索引,可以快速定位到相应的数据组,如图2-57所示。

图2-57 模拟器运行效果

表格视图的两种显示方式:索引表和分组表已经作了基本的介绍,在下一节中将介绍如何在多视图应用中使用导航控制器与标签控制器实现视图切换。

2.9 · 实现视图切换

导航控制器(NavigationController)是用于构建分层应用程序的主要工具,在管理及换入换出多个内容视图方面很常用。NavigationController 是作为栈(stack)来实现的,因此它非常适合处理分层的数据。

栈是一种常用的数据结构,采用后进先出的原则。向栈中添加对象的操作称为入栈(push),从栈中删除对象的操作称为出栈(pop)。当让一个对象出栈时,这个对象应该是最后一个入栈的,而第一个入栈对象是最后一个出栈的。

导航控制器适合于管理树状关系的视图;而当少数几个视图具有并列关系时,就需要考虑使用标签控制器了。与导航控制器一样,应当直接使用标签控制器,而不是去继承它。标签控制器有一个 viewControllers 属性,包含了每一个标签所对应的视图控制器。如果向viewControllers 中添加了6个或6个以上的视图控制器,那么在标签条中只会显示4个标签和一个More选项。当用户点击More时,剩下的标签会显示出来。

在上一节的例子中已经使用到了导航控制器,在这一节中将使用导航控制器与标签控制器做一个示例。打开Xcode→Create a new Xcode project→选择Application模板“Single View Application”→Next→填写项目名称:MultipleViews→Next→选择项目保存位置→Create。

创建完成之后,新建 7 个控制器,分别为 IndexViewController、InfoViewController、BlueViewController、RedViewController、GreenViewController、YellowViewController 和GrayViewController。IndexViewController和InfoViewController用来演示导航控制器,其余的几个控制器用来演示标签控制器。

修改MultipleViewsAppDelegate.h 接口文件,代码如下:

@interface MultipleViewsAppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UITabBarController *tabBarController;
@end

修改MultipleViewsAppDelegate.m 实现文件,代码如下:

#import "MultipleViewsAppDelegate.h"
#import "IndexViewController.h"
#import "BlueViewController.h"
#import "RedViewController.h"
#import "GreenViewController.h"
#import "YellowViewController.h"
#import "GrayViewController.h"
@implementation MultipleViewsAppDelegate
@synthesize window = _window;
@synthesize tabBarController = _tabBarController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary
  *)launchOptions
{
    // Override point for customization after application launch.
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    UIViewController *indexViewController = [[IndexViewController alloc] initWithNibName:
    @"IndexViewController" bundle:nil];
    UINavigationController *naviIndex=[[UINavigationController alloc] initWithRootViewController:
    indexViewController];
    UIViewController *blueViewController = [[BlueViewController alloc] initWithNibName:
    @"BlueViewController" bundle:nil];
    UINavigationController*naviBlue=[[UINavigationController alloc]initWithRootViewController:
    blueViewController];
    UIViewController *redViewController = [[RedViewController alloc] initWithNibName:
    @"RedViewController" bundle:nil];
    UINavigationController*naviRed=[[UINavigationController alloc] initWithRootViewController:
    redViewController];
    UIViewController *greenViewController = [[GreenViewController alloc] initWithNibName:
    @"GreenViewController" bundle:nil];
    UINavigationController*naviGreen=[[UINavigationController alloc] initWithRoot
    ViewController: greenViewController];
    UIViewController *yellowViewController = [[YellowViewController alloc] initWithNibName:
    @"YellowViewController" bundle:nil];
    UINavigationController*naviYellow=[[UINavigationController alloc] initWithRoot
    ViewController: yellowViewController];
    UIViewController *grayViewController = [[GrayViewController alloc] initWithNibName:
    @"GrayViewController" bundle:nil];
    UINavigationController *naviGray = [[UINavigationController alloc] initWithRoot
    ViewController:grayViewController];
    self.tabBarController = [[UITabBarController alloc] init];
    self.tabBarController.viewControllers = [NSArray arrayWithObjects: naviIndex,
    naviBlue, naviRed, naviGreen, naviYellow, naviGray, nil];
    self.window.rootViewController = self.tabBarController;
    [self.window makeKeyAndVisible];
    return YES;
}
......

下面来分析代码。

UIViewController *indexViewController = [[IndexViewController alloc] initWithNibName:
@"IndexViewController" bundle:nil];
UINavigationController *naviIndex = [[UINavigationController alloc] initWithRootView
Controller:indexViewController];

首先实例化控制器对象,然后用initWithRootViewController: 方法将控制器对象添加到导航控制器中。

self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers =[NSArray arrayWithObjects: naviIndex, naviBlue,
naviRed, naviGreen,naviYellow, naviGray, nil];

这两行代码实例化标签导航对象,然后将前面实例的几个导航控制器作为数组设置为标签控制器的 viewControllers 属性。这样点击标签控制器后切换的每个视图都会带有导航控制器。

设置完成之后打开IndexViewController.xib文件,选中视图,修改视图属性,将“Top Bar”项设置为“Navigation Bar”,然后往视图中拖入一个表格,如图2-58所示。

图2-58 将“Top Bar”项设置为“Navigation Bar”

接下来选中Table View并打开Connections面板,将dataSource与delegate后面的圆圈拖到“File's Owner”图标上,完成数据源与数据委托链接之后,返回IndexViewController控制器。

修改IndexViewController.h接口文件,代码如下:

@interface IndexViewController : UIViewController
<UITabBarDelegate, UITableViewDataSource>
{
    NSArray *tableIndexData;
}
@property (nonatomic, retain) NSArray *tableIndexData;
@end

修改IndexViewController.m实现文件,代码如下:

#import "IndexViewController.h"
#import "InfoViewController.h"
@implementation IndexViewController
@synthesize tableIndexData;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self)
    {
        self.title = @"首页";
        self.tabBarItem.image = [UIImage imageNamed:@"index.png"];
        self.tabBarItem.title =  @"首页";
    }
    return self;
}
- (void)didReceiveMemoryWarning
{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
    self.tableIndexData = [[NSArray alloc] initWithObjects:@"收件箱", @"草稿", @"已发
    送", @"废纸篓", nil];
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
}
- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark - Table View Methods
// 表格数据行数
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.tableIndexData count];
}
// 表格数据
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:
    (NSIndexPath *)indexPath
{
    static NSString *tableIdentifier = @"tableIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:tableIdentifier];
    if(cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithFrame:CGRectZero];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }
    cell.textLabel.text = [self.tableIndexData objectAtIndex:[indexPath row]];
    return cell;
}
- (void) tableView : (UITableView *) aTableView didSelectRowAtIndexPath : (NSIndexPath *)
    indexPath
{
    InfoViewController *infoViewController = [[InfoViewController alloc] initWithNibName:
    @"InfoViewController" bundle:nil];
    [self.navigationController pushViewController:infoViewController animated:YES];
}
@end

下面3行分别设置了导航控制器的标题、标签控制器的图片与标签控制器的标题。

self.title = @"首页";
self.tabBarItem.image = [UIImage imageNamed:@"index.png"];
self.tabBarItem.title =  @"首页";

其余的部分内容在上一节的表格视图中已经介绍了,代码基本一样,输出了一个索引表。

InfoViewController 控制器中只输出了一段文字,现在点击“Run”按钮或按下快捷键“+R”运行程序,效果如图2-59与图2-60所示。

图2-59 模拟器运行效果

图2-60 模拟器运行效果

现在点击导航控制器与标签控制器的“首页”按钮都可以回到首页。

依次打开 BlueViewController、RedViewController、GreenViewController、YellowView Controller和GrayViewController 视图文件,将视图背景设置为不同的颜色,如图2-61所示。

图2-61 设置视图背景色

因为只是为了演示标签控制器,所以这5个视图内都没有任何内容,只以不同的颜色显示。设置完成之后,打开BlueViewController.m文件,修改代码如下:

#import "BlueViewController.h"
@implementation BlueViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self)
    {
        self.title = @"蓝色";
        self.tabBarItem.image = [UIImage imageNamed:@"colors.png"];
        self.tabBarItem.title =  @"蓝色";
    }
    return self;
}

如IndexViewController控制器一样,设置导航控制器的标题、标签控制器的图片与标题,这几个控制器也依样完成。

设置完成之后,点击“Run”按钮或是按下快捷键“+R”运行程序,效果如图2-62与图2-63所示。

图2-62 模拟器运行效果

图2-63 模拟器运行效果

至此,一个导航控制器与标签控制器的程序就基本完成了,可以尝试在此基础上添加更多的视图。

2.10 · 本章总结

在本章中介绍了 Xcode 的基础知识,了解视图管理中心 Interface Builder;学习如何创建视图控制器,如何实现视图的旋转,实现表格视图的两种显示方式:索引表与分组表,使用导航控制器与标签控制器做一个简单的应用程序。

本章的例子较多,下面是章节与示例程序的对应关系。

2.5 HelloWorld

2.6 Autorotate

2.7 ViewObjects

2.8 TableView

2.9 MultipleViews