在先前的博文当中,我们 初步 了解了 列表视图,并且 创建了 一个简单的列表视图应用程序。不过 我们先前创建的列表视图应用程序 并不具备 导航功能。然而 在大多数情况中 表格视图 都要 和 导航功能 结合起来,这样 才能 使 用户 在不同的视图当中 切换。于是 在这一篇博文当中 我们 主要 关注 如何创建 具备导航功能的列表视图应用程序。
理解 导航控制器
具备导航功能的应用程序 会利用 成体系的方法 向用户 展示 信息。这样的应用程序 一般 包含 一个导航栏(UINavigationBar类型的物件) 和 一些列表视图。用户 点选 列表视图中的某个项目后,与之相应的视图 就会显示 在用户面前。同时 导航栏 会显示出 当前视图的标题 和 一个返回按钮,以便 用户 能够 回到 先前的列表视图。你 可以花 点时间,好好 用用 iphone的邮件应用程序 和 音乐应用程序,从而 更好地 理解 这个概念。
当我们 开发 具有导航功能的应用程序时,核心 是 导航控制器。另外 每个视图 都需要 一个相应的视图控制器。导航控制器 会维护着 一个由许多视图控制器组成的堆栈。每当一个视图 显示出来时,相应的视图控制器 就会被推到 导航控制器堆栈的顶端 变为 活跃的状态。导航控制器 会 自动 显示出 导航栏 和 返回按钮。当用户 点击 导航栏上的返回按钮时,当前活跃的视图控制器 就会被导航控制器 从堆栈的顶端 推走,而 被推走的视图控制器下方的那个视图控制器 就会成为 当前活跃的视图控制器,这样 用户 就回到了 上一级视图。
应用程序 启动时,第一个列表视图所在的视图控制器 叫做 底层视图控制器。而 底层视图控制器 不能从导航控制器堆栈中 被推走。
大致 了解 接下来的例子
在接下来的例子当中 我们 要 同时 实现 列表视图 和 导航功能。完成过后 我们的应用程序 会显示出 一个列表视图,这个列表视图 会列举出 歌手的名字。当我们 选择 某个歌手的名字后,会有 另外一个列表视图 显示出来,并且 列举出 这位歌手演唱的曲目。
既然我们这个例子的目的 是 同时 实现 列表视图 和 导航功能 而不是 处理 数据,所以 我们 仅仅 用 很简单的代码 来为 歌手列表 和 曲目列表 提供 数据。在实际应用中 列表视图所需的数据 都是 从数据库中 拉取的。
创建 项目
为了实现 我们这个例子,我们 需要创建 一个新的项目。于是 我们 启动 Xcode,然后 点击 Create a new Xcode project选项。在左边面板中 选择 iOS Application,再 在右边的面板中 选择 Empty Application(空应用程序)。点击 下一步后,将 产品名称 和 物件类型名称前缀 都设定为 TableView。接着 将 设备家族 设定为 iphone,而 其他选项 都取消。最后 点击 下一步,将 这个项目 存储 在适当的位置。
添加 底层视图控制器
由于我们 选择了 空应用程序模板,所以 Xcode 仅仅 为我们 生成了 应用程序代理的文件。于是 第一步 我们 需要 给这个项目 添加 底层视图控制器。
先前 提到过 一个导航控制器 需要 一个底层视图控制器。当这个导航控制器 被创建出来时,这个底层视图控制器中的视图 就是 呈现给用户的第一个视图。在考虑 导航控制器之前,我们 先要创建 底层视图控制器 及 其所包含的视图。底层视图控制器 必须属于 UIViewController类型 或者 这个类型中的小类。既然 我们这个例子的目标 是 实现 列表视图,所以 我们 需要使用到 UITableViewController类型的物件。
按住 键盘上的Control键,同时 用 鼠标 点击 Xcode窗口左侧项目导航面板顶端的TableView。再 在弹出的菜单中 点击 New File…选项。在新弹出的小窗左侧面板中 选择 iOS标题之下的Cocoa Touch选项,接着 选择 Objective-C class文件模板后 点击 下一步。在接下来的窗口中 将 Class标签右侧的文本框 填写成 RootViewController,表明 我们 要创建 一类叫做RootViewController的物件。然后 在Subclass of选项中 选择 UITableViewController,表明 每个RootViewController类型的物件 同时 也属于 UITableViewController类型。别忘了 勾选 With XIB for user interface选项,而 Targeted for iPad选项 则不管。点击 下一步后,Xcode 会为RootViewController这类物件 生成 RootViewController.h、RootViewController.m 和 RootViewController.xib三个文件:
创建 导航控制器
下一步 我们 需要编写 一些代码 来创建 导航控制器,并且 将 底层视图控制器 添加 到导航控制器当中。创建导航控制器的理想位置 是 应用程序代理。首先 我们 选择 TableViewAppDelegate.h这个文件,并且 在
这行语句之前 添加 这样一行语句:
这样 每个TableViewAppDelegate类型的物件中 都包含 一个变量navigationController,用于存储 导航控制器的地址。
接着 我们 要生成navigationController 和 setNavigationController:这两项措施。我们 选择 TableViewAppDelegate.m这个文件,并且 在@implementation命令后 加上
这行语句。
由于 应用程序 启动后,会创建 一个TableViewAppDelegate类型的物件,并且 对 这个物件 采取 application:didFinishLaunchingWithOptions:这项措施,所以 我们 打开 TableViewAppDelegate.m这个文件 并且 找到 application:didFinishLaunchingWithOptions:这项措施,将 这项措施 修改为 下面这样:
在这项措施中
这行语句 利用 RootViewController.xib这个文件中的设计 创建了 一个RootViewController类型的物件 并且 将 其地址 存储 在变量rootViewController当中。这个RootViewController类型的物件 我们 用作 底层视图控制器。
另外 为了使用 RootViewController类型的物件,别忘了 在TableViewAppDelegate.h中 加入
这行语句 或者
这行语句
这行语句 创建了 一个导航控制器 并且 将 上一行语句中创建的底层视图控制器 添加 到 这个导航控制器中。最后 将 这个导航控制器的地址 存储 在TableViewAppDelegate类型物件所包含的变量navigationController当中。
这行语句中 我们 先向UIScreen这类物件 发送了 mainScreen这条消息 从而 得到了 主屏幕物件。接着 我们 对主屏幕物件 采取了 bounds这项措施 从而 得到了 主屏幕的横、纵坐标 和 高、宽。然后 我们 给窗口物件,也就是 UIWindow类型的物件,分配了 内存地址,并且 对 其 采取了 initWithFrame:这项措施 从而 将 这个窗口物件的位置、尺寸 设定为 与主屏幕一致的数值。最后 我们 把 这个窗口物件的地址 存储 在TableViewAppDelegate类型物件所包含的变量window当中。
这行语句 向UIColor这类物件 发送了 whiteColor这条消息 从而 获得了 代表白色的物件。然后 将 TableViewAppDelegate类型物件所包含的窗口物件window的背景颜色 设定为 白色。
这行语句 将 导航控制器物件中所包含的视图 作为子视图 添加到 窗口物件上。
这行语句 对 应用程序的窗口物件 采取了 makeKeyAndVisible这项措施,将 其 变为 关键窗口 并且 肉眼可见。
在底层视图控制器中 组织 数据
底层视图控制器 将要扮演 数据源 和 列表视图代理的角色。所以 我们 需要编写 一些适用于底层视图控制器的措施 用来组织 数据 和 实现 列表功能。
首先,我们 打开 RootViewController.h这个文件,再 在@end命令前 加入 这行语句:
这样 每个RootViewController类型的物件 都包含 一个数组物件singers 用于存储 歌手的名字。
接着,我们 打开 RootViewController.m这个文件 并且 在@implementation命令后 加入
这行语句,从而 生成 singers 和 setSingers:这对措施。
一旦 底层视图控制器中包含的视图 加载后,就会 对底层视图控制器 采取 viewDidLoad这项措施,所以 我们 要在viewDidLoad这项措施当中 组织 数据。在RootViewController.m这个文件中 找到 viewDidLoad这项措施 并且 将 其 修改为 这样:
在这项措施中 我们 为 数组物件 分配了 内存地址 并且 将 两位歌手的名字 装进 这个数组物件。最后 将 这个数组物件的地址 存储 在底层视图控制器所包含的变量singers当中。
底层视图控制器中的视图 卸载后,就会 对底层视图控制器 采取 viewDidUnload这项措施。所以 我们 需要编写 viewDidUnload这项措施,就像 这样:
从而 将 底层视图控制器所包含的数组物件singers 从内存 清理出去。
编写 代码 在列表视图中 显示 数据
当列表视图 显示出来时,我们 必须告诉 列表视图 要显示 多少组数据 以及 多少排数据。通过采取 numberOfSectionsInTableView 和 numberOfRowsInSection这两项措施,列表视图 就能知道 需要显示 多少组数据 以及 多少排数据。于是 我们 打开 RootViewController.m这个文件。再 找到 numberOfSectionInTableView这项措施 并且 修改成 这样:
采取 这项措施后 得到的结果 是 整数1,表明 列表视图 要显示出 一组数据。
接着 找到 numberOfRowsInSection这项措施 并且 修改成 这样:
在这项措施中 我们 对底层视图控制器所包含的数组物件singers 采取了 count这项措施 从而 数出 数组物件singers中元素的数量,也就是 歌手名字的数量。然后 将 歌手名字的数量 作为结果 传递回去。列表视图 就会显示出 同样数量的单元格。
列表视图 知道了 要显示 多少组数据 和 多少排数据,接着 我们 要为列表视图的各排 创建 单元格 并且 把 每排需要显示的数据 存储 到这一排的单元格物件中。这个 可以通过 对 底层视图控制器 采取 cellForRowAtIndexPath这项措施 办到。还是 在RootViewController.m这个文件当中,我们 找到 cellForRowAtIndexPath这项措施 并且 将 这项措施 修改成 这样:
在这项措施中 Xcode 已经帮助 我们 完成了 大多数代码。这些代码的作用 在先前的文章中 已经介绍过了。cellForRowAtIndexPath这项措施 附带了 一个参数 就是 indexPath 表示 索引路径。而 唯一需要我们编写的代码 就是 这一行:
在这行代码中 我们 对索引路径indexPath 采取了 row这项措施,从而 获取 当前的排数。接着 将 当前的排数 作为参数 传递给 objectAtIndex:这项措施 并且 对数组物件singers 采取了 objectAtIndex:这项措施,从而 将 与当前排数所对应的歌手名字 读取出来。最后 将 当前单元格cell所包含的标签textLabel中的文本 设定为 与当前排数相对应的歌手名称。
最后
这行语句 将 我们准备好的单元格cell 作为结果 传递回去。
我们 编译 并且 运行 这个程序,可以看到 如下的结果:
根据 我们的设想,如果 我们 现在 点击 其中一个歌手的名字,就会弹出 另外一个列表视图 并且 列举出 这位歌手所演唱的曲目。但是 到目前为止 如果 我们 点击 任何一个歌手的名字,什么事情 都不会发生。这 正是 我们下一步要完成的工作。
创建 第二个视图控制器
为了 创建 显示某个歌手所唱曲目的视图控制器,我们 按住 Ctrl键 并且 点击 Xcode窗口左侧项目导航器上方的TableView条目,接着 在弹出的菜单中 点击 新文件(New File…)。在接下来的弹窗左侧的iOS部分 点选 Cocoa Touch。然后 在弹窗左侧 点击 Objective-C class模板。最后 点击 下一步(Next)。
点击 下一步过后,会出现 一个新的窗口。在新的窗口中 将 物件类型名称Class 设定为 SongsViewController 并且 将 这类物件所属的类别Subclass of 设定为 UITableViewController。这 就表示 我们要创建一类SongsViewController类型的物件,同时 这类物件 包含 在UITableViewController这类物件当中。然后 我们 要确保 没有选择 Targeted for iPad这个选项,还要确保 选择了 With XIB for user interface这个选项。
最后 点击 下一步,将 SongsViewController这类物件的文件 存储 在合适的地方。
接下来 我们 打开 RootViewController.h这个文件,并且 将 这个文件 修改成 这样:
这样一来 每个 底层视图控制器 都包含 一个SongsViewController类型的物件songsViewController 用于显示 某个歌手所唱曲目。
将 第二个视图控制器 和 底层视图控制器 连接起来
接着 我们 需要 在RootViewController.xib这个文件中 创建 一个SongsViewController类型的物件 并且 将 其 与RootViewController.h中创建的SongsViewController类型的物件songsViewController 连接起来。于是 我们 打开 RootViewController.xib这个文件 并且 从物件库中 拖 一个视图控制器(UIViewController类型) 放 到画布中。这样一来 我们 可以看到 每个RootViewController类型的物件 都包含 两个物件:
然后 我们 点击 刚刚添加到画布中的视图控制器 并且 点击 Xcode窗口右侧面板中第三个标签 进入 属性查看器。接着 将 新添加的视图控制器的类型 由UIViewController 改为 SongsViewController:
最后 按住 control键 并且 点击 File’s Owner图标:
鼠标 不放 一直 拖 到Songs View Controller图标:
放开 鼠标后,就会弹出 这样的菜单:
点击 songsViewController选项后,RootViewController.h中创建的SongsViewController类型的物件songsViewController 和 RootViewController.xib中的SongsViewController类型的物件 就是 同一个物件了。
实现 第二个视图控制器的功能
因为 SongsViewController类型的物件 用来充当 其所包含的列表视图的数据源,所以 我们 要编写 一些代码 来存储 各位歌手所唱的相应曲目。
首先 我们 打开 SongsViewController.h这个文件 并且 将 其 修改成 这样:
从而 每个SongsViewController类型的物件 都包含 一个数组物件songs 用以存储 某个歌手所唱的曲目。接着 我们 需要生成 songs 和 setSongs:这两项措施 以便 对每个SongsViewController类型的物件所包含的songs物件 进行读写。于是 我们 打开 SongsViewController.m这个文件 并且 在@implementation命令后 加上 这行语句:
同时 我们 需要确定 SongsViewController类型物件中的列表视图 需要显示 多少分组 以及 每个分组 显示 多少排数据。所以 我们 在SongsViewController.m这个文件中 找到 numberOfSectionInTableView 和 numberOfRowsInSection这两项措施 并且 将 他们 修改成 这样:
这样一来 SongsViewController物件中的列表视图 就会显示出 一个分组,这个分组显示的数据排数 与 当前歌手所唱的曲目数量 一致。
下一步任务 是 将 各个歌手所唱的曲目 存储 在SongsViewController物件所包含的数组songs当中。我们 可以在viewDidLoad这项措施当中 完成 这个任务。SongsViewController类型物件中的视图 加载时 就会采取 viewDidLoad这项措施。直到SongsViewController类型物件中的视图 被清理 并且 再次 加载时,viewDidLoad这项措施 才会得以执行。如果 我们 初始化 数据 一次,viewDidLoad这项措施 是 理想的选择。但是 在我们现在这个应用程序中,用户 每次 可能选择 不同的歌手。所以 SongsViewController类型物件中的视图 每显示出来一次,我们 都需要重新初始化 这个物件中的数组songs。于是 我们 需要找到 一项措施,而且 每次 SongsViewController类型物件中的视图 显示出来时,这项措施 都要能够得以执行。而 viewWillAppear这项措施 每当 视图控制器中的视图 即将显示出来时 都会得以执行,所以 我们 应该在viewWillAppear这项措施当中 对SongsViewController类型物件中的数组songs 进行初始化。我们 打开 SongsViewController.m这个文件 并且 添加 viewWillAppear这项措施:
在这项措施中 我们 利用 SongsViewController类型视图控制器的标题 来判断 用户 选择了 哪位歌手。在后面的代码中 我们 会看到 当用户 在底层视图控制器的列表视图中 选择 某个歌手后,我们 会把 显示这个歌手所唱曲目的SongsViewController类型视图控制器的标题 设定为 这位歌手的名字。
这行语句 对SongsViewController类型物件所包含的列表视图tableView 采取 reloadData这项措施。这样 可以保证 每次SongsViewController类型物件中的视图 显示出来时,其中的列表视图 都会显示 最新的数据。
当SongsViewController类型物件中的视图 卸载时,我们 要保证 这个物件中的数组songs 也被清理掉,所以 我们 在SongsViewController.m这个文件中 加入 下面的措施:
接下来 我们 需要为SongsViewController类型物件中的列表视图 创建 单元格。我们 在SongsViewController.m这个文件中 找到 cellForRowAtIndexPath:这项措施 并且 修改成 这样:
cellForRowAtIndexPath这项措施 在执行的时候 会收到 参数indexPath,也就是 索引路径。
这行语句 对索引路径indexPath 采取了 row这项措施,从而 得知 当前正在准备第几排的单元格。然后 对SongsViewController类型物件所包含的数组songs 采取objectAtIndex:这项措施 从而 将 与当前单元格对应的曲目名称 读取出来。最后 将 与当前单元格对应的曲目名称 存储 在正在准备的单元格cell当中。
这行语句 将 准备好的单元格cell 作为结果 传递回去。
编写 代码 实现 导航功能
接下来 我们 需要修改 底层视图控制器的功能,以便 用户 在底层视图控制器中 选择 某个歌手后,显示 这个歌手所唱曲目的视图控制器,也就是 SongsViewController类型的物件,能够显示出来。当用户 选择 列表视图中的某个项目时,didSelectRowAtIndexPath这项措施 就会得以实施。所以 我们 要在didSelectRowAtIndexPath这项措施中 实现 导航功能。
首先 我们 打开 RootViewController.m这个文件 并且 在@implementation命令后 加上 这行语句
这样一来 我们 就生成了 songsViewController 和 setSongsViewController:这两项措施,我们 就可以对 底层视图控制器中所包含的SongsViewController类型的物件songsViewController 进行访问了。
然后 在RootViewController.m这个文件中 找到 didSelectRowAtIndexPath这项措施 并且 将 这项措施 修改为 下面这样:
在这项措施中 我们 先判断 用户 选择了 列表视图中的第几排。然后 根据用户的选择 将底层视图控制器中所包含的视图控制器songsViewController的标题 设定为 相应的歌手名称。
这行语句 对底层视图控制器所包含的导航控制器navigationController 采取了 pushViewController:animated:这项措施 并且 将 底层视图控制器所包含的视图控制器songsViewController 作为参数,从而 将 songsViewController这个视图控制器 推到 导航控制器堆栈的顶端。如果 要把导航控制器堆栈顶端的视图控制器 推开,我们 需要对导航控制器 采取 popViewControllerAnimated这项措施。
编译 并且 运行 这个程序后,我们 任意 点击 一位歌手的名字,我们 可以看到 这样的效果:
这时候 我们看到的就是 就是 SongsViewController类型的视图控制器物件songsViewController。而 songsViewController这个物件 就包含 在RootViewController类型的底层视图控制器物件当中。