UITableView有一个editing属性,当这个属性被设置为true时,table view进入编辑模式。在编辑模式,可以修改行顺序,增加行,删除行,但是不能修改行内容。
UITableView有header view和footer view,他们可以是section范围的,也可以是整个table范围的。
通过XIB为table view添加header view,先在XIB的File's Owner中声明IBOutlet和IBAction。
#import "BKItemsViewController.h"
#import "BKItem.h"
#import "BKItemStore.h"
@interface BKItemsViewController ()
@property (nonatomic, strong) IBOutlet UIView *headerView;
@end
@implementation BKItemsViewController
// .....
- (IBAction)addNewItem:(id)sender{
}
- (IBAction)toggleEditingMode:(id)sender{
}
@end
headerView属性声明为strong,是因为他是XIB文件中的top-level object,top-level object拥有的对象声明为weak的。
XIB文件不仅可以用来创建view controller的view,还可以布局view对象,然后在运行时加载他们。
XIB files are typically used to create the view for a view controller, but they can also be used any time you want to lay out view objects, archive them, and have them loaded at runtime.
创建XIB
创建一个空的user interface,选择File's Owner,修改其Class为BKItemsViewController
view的大小默认是屏幕大小,不可调整,可以设置其size为none,使其大小可以自由调整。
还是在上图的attributes inspector中,将view的background设置为clear color.
将view和File's Owner关联起来:
将两个button的action连接到两个方法,将File's Owner的headerView属性和view关联起来,这样当加载XIB文件时,headerView就是这个关联的view。
手动加载XIB文件,需要使用NSBundle,当应用启动后,就会有一个对应的NSBundle实例,要获得此实例,调用[NSBundle mainBundle]
,加载完后,将headerView设置给table view。
#import "BKItemsViewController.h"
#import "BKItem.h"
#import "BKItemStore.h"
@interface BKItemsViewController ()
@property (nonatomic, strong) IBOutlet UIView *headerView;
@end
@implementation BKItemsViewController
// ...
- (void)viewDidLoad{
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"UITableViewCell"];
// 设置table view的header view
UIView *header = self.headerView;
[self.tableView setTableHeaderView:header];
}
// headerView属性的get方法,当第一次调用此方法时,加载对应的XIB文件
- (UIView *)headerView{
if(!_headerView){
// 加载 HeaderView.xib
[[NSBundle mainBundle] loadNibNamed:@"HeaderView" owner:self options:nil];
}
return _headerView;
}
@end
启用table view编辑模式
不仅UITableView有一个editing属性,UITableViewController也有一个editing属性,table view controller会自动设置table view's editing property和自己的editing property保持一致。
要设置table view controller的editing property,需要发送setEditing:animated:
message.
- (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];
}
}
添加行
为table view插入一行
- (IBAction)addNewItem:(id)sender{
// 为要插入的行创建index path
NSInteger lastRow = [self.tableView numberOfRowsInSection:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:lastRow inSection:0];
// 插入一行
[self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
}
此时运行,应用崩溃程序出错,因为table view的行是由其dataSource决定的,这里只为table view插入一行,但是其dataSource中却没有这行的数据,所以出错。
必须保证table view的行数和其dataSource中的数量一致。所以在插入一行之前,先添加数据到dataSource中。
- (IBAction)addNewItem:(id)sender{
// 为要插入的行创建index path
//NSInteger lastRow = [self.tableView numberOfRowsInSection:0];
// 新建一条数据
BKItem *newItem = [[BKItemStore sharedStore] createItem];
NSInteger lastRow = [[[BKItemStore sharedStore] allItems] indexOfObject:newItem];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:lastRow inSection:0];
// 插入一行
[self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
}
在插入一行时,发送了tableView message给BKItemsViewController,tableView属性继承自UITableViewController,指定一个UITableView实例,view属性其实和tableView属性指向同一个实例,但是view属性的类型是UIView。
删除行
当table view要删除一行时,会发送消息给data source,并等待确认后才会删除。
要删除一行,需要两个动作:tabel view删除一行,数据源也删除对应的行数据。
table view的dataSource是BKItemViewController,BKItemStore只是一个存放和操作数据的地方
为BKItemStore添加删除方法:
#import <Foundation/Foundation.h>
@class BKItem;
@interface BKItemStore : NSObject
@property (nonatomic, readonly) NSArray *allItems;
// class method
+ (instancetype)sharedStore;
- (BKItem *)createItem;
- (void)removeItem:(BKItem *)item;
@end
在实现文件中实现
- (void)removeItem:(BKItem *)item{
//[self.privateItems removeObject:item];
[self.privateItems removeObjectIdenticalTo:item];
}
removeObject和removeObjectIdenticalTo的区别:
method | desc |
---|---|
removeObject: | 传递的对象只要和数据中的对象isEqual:就会被删除 |
removeObjectIdenticalTo: | 删除==的对象 |
在table view的delegate,即BKItemsViewController中实现tableView:commitEditingStyle:forRowAtIndexPath:
方法,当要真正删除时发送deleteRowsAtIndexPaths:withRowAnimation:
返回给table view。
// 当table view要删除一行时,会发送此消息到其delegate,这个方法是在UITableVieaDataSource protocol中定义的
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
// 当是要执行删除时
if(editingStyle == UITableViewCellEditingStyleDelete){
NSArray *items = [[BKItemStore sharedStore] allItems];
BKItem *item = items[indexPath.row];
// 删除cell对应的item
[[BKItemStore sharedStore] removeItem:item];
// table view 删除行
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
移动行
首先为操作数据的类BKItemStore添加一个方法,来修改数据的顺序。
在头文件中声明
- (void)moveItemAtIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex;
- (void)moveItemAtIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex{
if (fromIndex == toIndex) {
return;
}
BKItem *item = self.privateItems[fromIndex];
[self.privateItems removeObjectAtIndex:fromIndex];
[self.privateItems insertObject:item atIndex:toIndex];
}
当table view要更改行顺序时,需要发送tableView:moveRowAtIndexPath:toIndexPath:
给其代理,在BKItemsViewController中实现此方法,当table view的代理实现了此方法,那么table view的编辑模式就会出现 reordering controls(就是列右侧的三个横线)。
// 移动行顺序
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{
[[BKItemStore sharedStore] moveItemAtIndex:sourceIndexPath.row toIndex:destinationIndexPath.row];
}
本文是对《iOS Programming The Big Nerd Ranch Guide 4th Edition》第九章的总结。