UITableView有一个名为editing的属性,如果将editing属性设置为YES,UITableView就会进入编辑模式。在编辑模式下,用户可以管理UITableView中的表格行,例如之前提到的添加、删除和移动等操作。但是编辑模式没有提供修改行的内容的功能。
首先需要更新界面,使用户可以将UITableView对象设置为编辑模式。本章是为UITableView对象的表头视图(header view)增加一个按钮,然后通过点击按钮使UITableView对象进入或退出编辑模式。表头视图是指UITableView对象可以在其表格上方显示的特定视图,适合放置针对某个表格段或整张表格的标题和控件。表头视图可以是任意的UIView对象。
表头视图有两种,分别针对表格段和表格。类似地,还有表尾视图(footer view),也具有表格段和表格两种(见图9-2)。
图9-2 针对表格段的表头视图和表尾视图
接下来创建一个针对表格的表头视图。这个表头视图包含两个UIButton对象,其中一个负责切换UITableView对象的编辑模式,另一个负责创建新的BNRItem对象并加入UITableView对象。可以使用代码创建这个表头视图及其包含的子视图,但是本章将使用XIB文件创建它们。之后,BNRItemsViewController对象在需要显示表头视图时会加载相应的XIB文件。
首先需要编写一些代码。重新打开第8章中的Homepwner.xcodeproj,在BNRItemsViewController.m中添加BNRItemsViewController的类扩展,然后声明一个插座变量并添加两个新方法,代码如下:
@interface BNRItemsViewController
@property (nonatomic, strong) IBOutlet UIView *headerView;
@end
@implementation BNRItemsViewController
// 这里省略了其他方法
- (IBAction)addNewItem:(id)sender
{
}
- (IBAction)toggleEditingMode:(id)sender
{
}
载入XIB文件后,headerView会指向XIB文件中的顶层对象,并且是强引用。指向顶层对象的插座变量必须声明为强引用;相反,当插座变量指向顶层对象所拥有的对象(例如顶层对象的子视图)时,应该使用弱引用。
下面创建一个新的XIB文件。和本书之前创建的XIB文件不同,这个XIB文件和视图控制器的视图无关(BNRItemsViewController作为UITableViewController的子类,可以自行创建其视图)。通常情况下,可以用XIB文件来创建某个视图控制器的视图。但是,也可以在XIB文件中随意创建多个视图并设置层级结构和布局,然后在运行应用时按需载入。
选择File菜单中的New菜单项,然后选择File…选中窗口左侧iOS部分的User Interface,然后选中窗口右侧的Empty模板,最后单击Next按钮(见图9-3)。
图9-3 创建新的XIB文件
在新出现的面板中选择Device Family下拉菜单中的iPhone,单击Next按钮。Xcode会提示保存文件,将文件名设置为HeaderView.xib,单击Save按钮。
选中HeaderView.xib中的File/'s Owner,打开标识检视面板,将Class文本框中的UIViewController修改为BNRItemsViewController(见图9-4)。
图9-4 修改File/'Owner
先拖曳一个UIView对象至画布,然后再拖曳两个UIButton对象至这个UIView对象。现在需要调整UIView对象的大小,但是读者会发现UIView对象的大小被锁定了,无法调整,因此需要解除大小锁定。选中UIView对象并打开属性检视面板,在Simulated Metrics部分中点击标题为Size的选项列表,选择None(见图9-5)。
图9-5 解除视图大小锁定
现在可以调整视图大小了,请按图9-6所示调整UIView对象的大小并创建相应的关联。
图9-6 HeaderView.xib的布局
还要将UIView对象的背景颜色修改为全透明颜色。具体做法为:选中之前加入的UIView对象并打开属性检视面板。单击标题为Background的颜色的选项列表,选择Clear Color(见图9-7)。
图9-7 将背景色设置为Clear Color
本书之前所创建的XIB文件,都是通过UIViewController类的默认实现自动载入的。以第6章的BNRReminderViewController为例,因为它是UIViewController的子类,所以BNRReminderViewController对象会在需要显示其视图时,自动载入BNRReminderViewController.xib。但是对于HeaderView.xib,则需要编写特定的代码,让BNRItemsViewController对象能够“手动”载入该XIB文件。
使用NSBundle类可以载入指定的XIB文件。该类是“应用程序包”和“应用程序包所包含的可执行文件”之间的接口。通过该类,应用可以访问某个程序包中的文件。向该类发送mainBundle消息可以得到指向主NSBundle对象的指针,该对象是应用在启动时创建的。
得到主NSBundle对象后,就可以要求其载入应用程序包中的某个XIB文件。在BNRItemsViewController.m中实现headerView方法,代码如下:
- (UIView *)headerView
{
// 如果还没有载入headerView...
if (!_headerView) {
// 载入HeaderView.xib
[[NSBundle mainBundle] loadNibNamed:@/"HeaderView/"
owner:self
options:nil];
}
return _headerView;
}
该方法使用了一种名为延迟实例化(Lazy Instantiation)的设计模式:只会在真正需要使用某个对象时再创建它。在某些情况下,这种设计模式可以显着减少内存占用。
调用loadNibNamed:owner:options:时,需要传入XIB文件的文件名。文件名不需要包含后缀,NSBundle会自行判断并处理。此外,这段代码将self作为owner实参(拥有者)传给了NSBundle对象,目的是当BNRItemsViewController对象将XIB文件加载为NIB文件时,使用BNRItemsViewController对象自身替换占位符对象File/'s Owner。
BNRItemsViewController对象会在第一次收到headerView消息时载入HeaderView.xib,然后为插座变量headerView赋值,并将其指向HeaderView.xib中的顶层UIView对象。当用户按下这个顶层UIView对象中的任何一个按钮时,BNRItemsViewController对象都会收到指定的动作消息。
加载了headerView后,还需要将其设置为UITableView对象的表头视图。在BNRItemsViewController.m的viewDidLoad方法中,添加以下代码:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class]
forCellReuseIdentifier:@/"UITableViewCell/"];
UIView *header = self.headerView;
[self.tableView setTableHeaderView:header];
}
构建并运行应用,UITableView对象会在列表上方显示两个按钮。
XIB文件不仅可以用来创建视图控制器的视图(例如BNRReminderView- Controller.xib),也可以用来固化其他的视图层次结构(例如HeaderView.xib)。实际上,任何对象都可以通过向主NSBundle对象发送loadNibNamed:owner:options:消息手动载入XIB文件。
前面介绍过,UIViewController默认已经实现了通过XIB文件载入视图的功能。其实现原理和headerView方法的相同,代码也相似。唯一的差别是,UIViewController对象会在载入XIB文件后,将插座变量view关联至XIB文件中指定的UIView对象。UIViewController的loadView方法的代码示例如下(这里列出的代码仅是举例,和UIViewController的loadView方法的实际代码不同):
- (void)loadView
{
// NIB文件位于哪个bundle中?
// 是否为initWithNibName:bundle:方法传了bundle参数?
NSBundle *bundle = [self nibBundle];
if (!bundle) {
// 使用默认bundle
bundle = [NSBundle mainBundle];
}
// NIB文件的名称是什么?
// 是否为initWithNibName:bundle:方法传了NIB文件名参数?
NSString *nibName = [self nibName];
if (!nibName) {
// 使用默认NIB文件名
nibName = NSStringFromClass([self class]);
}
// 尝试在bundle中查找默认NIB文件
NSString *nibPath = [bundle pathForResource:nibName
ofType:@/"nib/"];
// 该NIB文件是否存在?
if (nibPath) {
// 加载NIB文件(同时还会设置view插座变量)
[bundle loadNibNamed:nibName owner:self options:nil];
} else {
// 如果没有NIB文件,就创建一个空白视图
self.view = [[UIView alloc] init];
}
}
接下来实现toggleEditingMode:。虽然可以直接通过设置UITableView对象的editing属性来切换编辑模式,但是UITableViewController也有一个从UIViewController继承而来的editing属性。当某个UITableViewController对象的editing属性发生变化时,UITableViewController对象会同步修改其UITableView对象的editing属性。
向某个UIViewController对象或UIViewController子类对象发送setEditing: animated:消息,可以设置该对象的editing属性(UITableViewController覆盖了UIViewController的setEditing:animated:方法)。在BNRItemsViewController.m中实现toggleEditingMode:,代码如下:
- (IBAction)toggleEditingMode:(id)sender
{
// 如果当前的视图控制对象已经处在编辑模式…
if (self.isEditing) {
// 修改按钮文字,提示用户当前的表格状态
[sender setTitle:@/"Edit/" forState:UIControlStateNormal];
// 关闭编辑模式
[self setEditing:NO animated:YES];
} else {
// 修改按钮文字,提示用户当前的表格状态
[sender setTitle:@/"Done/" forState:UIControlStateNormal];
// 开启编辑模式
[self setEditing:YES animated:YES];
}
}
构建并运行应用,按下Edit按钮,UITableView对象会开启编辑模式(见图9-8)。
图9-8 编辑模式下的UITableView对象