10 Editing UITableView

31
编辑 UITableView 范圣刚,[email protected] , www.tfan.org

description

编辑 UITableView,编辑模式,增加行,删除行,移动行

Transcript of 10 Editing UITableView

Page 2: 10 Editing UITableView

编辑模式下的 Homepwner

•上⼀一节我们实现了把 BNRItem 的⼀一系列实例在 UITableView 中显⽰示的应⽤用

•下⼀一步我们将允许⽤用户和表格进⾏行交互 - 添加,删除和移动表格的⾏行

Page 3: 10 Editing UITableView

编辑模式

Page 4: 10 Editing UITableView

•UITableView 有⼀一个 editing 属性,并且当这个属性被设置成 YES 的时候, UITableView 就进⼊入了编辑模式。

•⼀一旦 table view 进⼊入编辑模式,表格中的⾏行就可以被⽤用户操作。⽤用户可以改变⾏行的顺序,添加新⾏行,或者删除⾏行。但是编辑模式并不允许⽤用户编辑表格⾏行本⾝身的内容

•怎么才能让⽤用户进⼊入编辑模式?我们将使⽤用在表格的 header view 中的⼀一个按钮来切换编辑模式

Page 5: 10 Editing UITableView

header view

• header view 出现在表格的⼀一个 section 的顶部,被⽤用来增加 section 范围或者 table 范围的标题和控件

•可以是任意 UIView 的实例

•同样也有在 section 底部的 footer view,原理是⼀一样的

Page 6: 10 Editing UITableView

我们⽤用到的 header view

•我们要创建的 header view 将会出现在 BNRItem 列表的顶部,将会包含两个⼦子视图,都是 UIButton 的实例•⼀一个⽤用来切换编辑模式•另⼀一个⽤用来往表格中增加⼀一个新的 BNRItem

•我们将在 XIB ⽂文件中创建这个视图,然后 ItemsViewController 将在其需要显⽰示这个 header view 的时候解压这个 XIB ⽂文件

Page 7: 10 Editing UITableView

headerView 相关的变量和⽅方法•⾸首先我们来设置必要的代码•在 ItemsViewController.h 中为我们的 header view 声明⼀一个 UIView 类型的实例变量和三个新的⽅方法

@interface ItemsViewController : UITableViewController{ IBOutlet UIView *headerView;}

- (UIView *)headerView;- (IBAction)addNewItem:(id)sender;- (IBAction)toggleEdittingMode:(id)sender;

Page 8: 10 Editing UITableView

XIB ⽂文件:HeaderView

•新的 XIB ⽂文件(File -> New -> File, iOS -> User Interface, Emtpy, 保存为: HeaderView)

•和我们之前创建的所有 XIB ⽂文件都不同,这个 XIB ⽂文件和 view controller 的 view 没有任何关系

•作为 UITableViewController 的⼦子类,ItemsViewController 总是知道如何⽣生成它的 view

• XIB ⽂文件通常被⽤用来为⼀一个 view controller ⽣生成 view,但是我们也可以在任何想要布局 view 对象的时候使⽤用它

Page 9: 10 Editing UITableView

File’s Owner 的类•⾸首先在 HeaderView.xib 中,选择 File’s Owner 对象,然后把它的 Class 改成 ItemsViewController

•然后拖曳⼀一个 UIView 到 canvas 区域,再拖曳两个 UIButton 的实例到这个视图上

•调整这个 UIView 的⼤大⼩小并按下图⽣生成连接

Page 10: 10 Editing UITableView

HeaderView 的布局和连接

Page 11: 10 Editing UITableView

view 的背景颜⾊色•同时要把 UIView 实例的颜⾊色改成完全透明

•在 view 的 attribute inspector 中,点击 Background 的颜⾊色选择器,然后把 Opacity 拖到 0(clear color)

Page 12: 10 Editing UITableView

加载 HeaderView.xib

•截⾄至⺫⽬目前,XIB ⽂文件的加载都是由 UIViewController 的实现⾃自动完成的

•⽐比如前⾯面的 TimeViewController 知道如何加载 TimeViewController.xib, 因为写在它的超类(UIViewController)中的代码

•对于 HeaderView.xib, 我们⼿手动来写⼀一些代码让 ItemsViewController 加载这个 XIB ⽂文件

•要⼿手动加载 XIB ⽂文件,我们要⽤用到 NSBundle

Page 13: 10 Editing UITableView

NSBundle 和 mainBundle

•NSBundle 类是 application 和 application bundle 之间的接⼝口

•当我们想要访问位于 application bundle 中的⽂文件时,我们向 NSBundle 请求它

•当应⽤用程序启动时,⼀一个 NSBundle 的实例就被创建,然后我们可以通过向 NSBundle 发送 mainBundle 消息获得指向这个实例的指针

•⼀一旦我们拿到了指向 main bundle 对象的指针,我们就可以请求它加载⼀一个 XIB ⽂文件

Page 14: 10 Editing UITableView

实现 headerView ⽅方法

• ⾸首先注意⼀一下 loadNibNamed:owner:options: ⽅方法的参数• 不需要加 XIB ⽂文件的扩展名,NSBundle 会⾃自⼰己搞定

• 我们是把 self 作为 XIB ⽂文件的 owner 传进去的,这就把 ItemsViewController 的实例填到了 XIB ⽂文件的 File’s Owner hole 了

• headerView 消息第⼀一次被发到 ItemViewController 时,它就会加载 HeaderView.xib 并且在实例变量 headerView 中保存⼀一个指向 view 对象的指针。当 view 上的按钮被按下时就会发消息到 ItemViewController

- (UIView *)headerView{ // 如果还没有加载 headerView ... if (!headerView) { // 加载 HeaderView.xib [[NSBundle mainBundle] loadNibNamed:@"HeaderView" owner:self options:nil]; } return headerView;}

Page 15: 10 Editing UITableView

两个 header view 相关的⽅方法•我们已经⽣生成了 headerView, 下⾯面就使它成为

table 的 header view。这需要在 ItemsViewController.m 中从 UITableViewDelegate protocol 实现两个⽅方法• tableView:viewForHeaderInSection:, 返回⼀一个 view

• tableView:heightForHeaderInSection:

•虽然这两个⽅方法在 protocol 中都是作为 optional 列出来的,但是想要 header view 的话,必须实现他们

Page 16: 10 Editing UITableView

headerView 的加载

• 实现着两个⽅方法之后,当 UITableView 需要显⽰示它的 header view 时,就会发送这些消息给它的 delegate,也就是 ItemsViewController

• 当 tableView:heightForHeaderInSection: 消息第⼀一次被发给 ItemsViewController 时,它会先发给⾃自⼰己⼀一个 headerView 消息。此时,headerView 还是 nil,这将会引起 headerView 被从 XIB ⽂文件中被加载

// 实现两个⽅方法使 headerView 成为 table 的 header view- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{ return [self headerView];}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{ // header view 的⾼高度应该由 XIB ⽂文件中视图的⾼高度决定 return [[self headerView] bounds].size.height;}

Page 17: 10 Editing UITableView

editing 属性• headerView 已经可以成为 table 的 header view 了,下⾯面来实现 toggleEditingMode: ⽅方法,也就是我们点击按钮切换编辑状态的⽅方法

•我们可以直接切换 UITableView 的 editing 属性。然⽽而,UITableViewController 也有⼀一个 editing 属性。⼀一个 UITableViewController 实例会⾃自动设置它的 table view 的 editing 属性来和它的 editing 属性相匹配。该⽤用哪⼀一个?遵循 MVC 模式:和 controller 打交道,让 controller 去和 view 打交道

Page 18: 10 Editing UITableView

setEditing:animated:

•要设置 view controller 的 editing 属性,就给它发送 setEditing:animated: 消息

- (IBAction)toggleEdittingMode:(id)sender{ // 如果我们已经在编辑模式 if ([self isEditing]) { // 改变按钮的⽂文本以通知⽤用户状态 [sender setTitle:@"编辑" forState:UIControlStateNormal]; // 关闭编辑模式 [self setEditing:NO animated:YES]; } else { [sender setTitle:@"完成" forState:UIControlStateNormal]; // 进⼊入编辑模式 [self setEditing:YES animated:YES]; }}

Page 19: 10 Editing UITableView

Editing mode 下的 UITableView

Page 20: 10 Editing UITableView

增加⾏行•我们使⽤用 header view 中的⼀一个“新建”按钮来给表格增加新⾏行,当按钮被按下时,⼀一个新⾏行将被增加到 UITableView。

•是 UITableView 的 dataSource 来决定 table view 要显⽰示的⾏行数,因此我们要确保 UITableView 显⽰示的⾏行数要和 dataSource 保存的⾏行数⼀一致

•因此我们在实现 addNewItem: 时,在往 table view 插⼊入⼀一⾏行前,要先把增加⼀一个新的 BNRItem 到 BNRItemStore 中

Page 21: 10 Editing UITableView

实现 addNewItem:

•发送 tableView 消息给 UITableViewController 将返回 controller 的 table view

•现在我们可以⾃自⼰己添加新⾏行了,可以把 init ⽅方法中往 store 中压⼊入 5 个随机 items 的代码删掉了

- (IBAction)addNewItem:(id)sender{// // 为第 0 个 section ⽣生成⼀一个新的 index path,最后⼀一⾏行// int lastRow = [[self tableView] numberOfRowsInSection:0]; BNRItem *newItem = [[BNRItemStore defaultStore] createItem]; int lastRow = [[[BNRItemStore defaultStore] allItems] indexOfObject:newItem]; NSIndexPath *ip = [NSIndexPath indexPathForItem:lastRow inSection:0]; // 把这个新⾏行插⼊入 table [[self tableView] insertRowsAtIndexPaths:[NSArray arrayWithObject:ip] withRowAnimation:UITableViewRowAnimationTop];}

Page 22: 10 Editing UITableView

删除⾏行•在删除模式下,带横线的红圈是删除控件,触控其中⼀一个应该删除那⼀一⾏行。然后,此时触控删除控件不会发⽣生任何事情。在 table view 将删除⼀一⾏行前,它会发送⼀一个有关拟删除的消息给它的数据源,并且在扣动扳机前会等待⼀一个确认消息

Page 23: 10 Editing UITableView

•在删除⼀一个单元格时,必须要做两件事情:•从 UITableView 删除那⼀一⾏行

•从 BNRItemStore 中删除和它关联的 BNRItem

• BNRItemStore 必须知道如何从其⾃自⾝身删除⼀一个对象,我们要在 BNRItemStore.h 中声明⼀一个新的⽅方法:removeItem:

Page 24: 10 Editing UITableView

removeItem:

• 可以使⽤用 removeObject: ⽅方法替换 removeObjectIdenticalTo: ,但是要注意它们的区别

• removeObject: ⽅方法遍历数组的每个对象并发送给它们⼀一个 isEqual: 消息。类可以实现这个⽅方法根据⾃自⼰己的判断来返回 YES or NO

@interface BNRItemStore : NSObject{ NSMutableArray *allItems;}

+ (BNRItemStore *)defaultStore;

- (NSArray *)allItems;- (BNRItem *)createItem;

- (void)removeItem:(BNRItem *)p;

- (void)removeItem:(BNRItem *)p{ [allItems removeObjectIdenticalTo:p];}

Page 25: 10 Editing UITableView

UITableViewCellEditingStyleDelete

•下⾯面我们实现 tableView:commitEditingStyle:forRowAtIndexPath:, ⼀一个来⾃自 UITableViewDataSource protocol 的⽅方法。

•当 tableView:commitEditingStyle:forRowAtIndexPath: 被发送给数据源时,两个额外的参数也被随着传递•第⼀一个是 UITableViewCellEditingStyle, 在这⾥里是

UITableViewCellEditingStyleDelete

•另⼀一个参数是表格中⾏行的 NSIndexPath

Page 26: 10 Editing UITableView

实现确认删除的⽅方法

•在 ItemsViewController.m 中实现这个⽅方法•让 BNRItemStore 删除正确的对象

•通过发回给table view deleteRowsAtIndexPaths:withRowAnimation: 消息确认对⾏行的删除

- (void)tableView:(UITableView *)tableViewcommitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{ if (editingStyle == UITableViewCellEditingStyleDelete) { BNRItemStore *ps = [BNRItemStore defaultStore]; NSArray *items = [ps allItems]; BNRItem *p = [items objectAtIndex:[indexPath row]]; [ps removeItem:p]; // 同时也把这⼀一⾏行从 table view 中删除 [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; }}

Page 27: 10 Editing UITableView

移动⾏行

Page 28: 10 Editing UITableView

•要改变⼀一个 UITableView 中⾏行的顺序,要使⽤用来⾃自 UITableViewDataSource 的另外⼀一个⽅方法:tableView:moveRowAtIndexPath:toIndexPath:

•前⾯面我们看到要删除⼀一⾏行的话,要发送 deleteRowsAtIndexPaths:withRowAnimation: 给 UITableView 来确认删除

•移动⼀一⾏行的话,不需要确认,只是通过发送 tableView:moveRowAtIndexPath:toIndexPath: 消息给它的数据源通知这个移动操作

•我们只需要捕捉这个消息来更新我们的数据源以匹配新的顺序

Page 29: 10 Editing UITableView

moveItemAtIndex:toIndex ⽅方法•和删除⾏行⼀一样,我们在实现这个数据源⽅方法之前先给 BNRItemStore ⼀一个⽅方法,⽤用于变更在它的 allItems 数组中的 BNRItem 的顺序。

•声明和实现 moveItemAtIndex:toIndex: ⽅方法- (void)moveItemAtIndex:(int)from toIndex:(int)to{ if (from == to) { return; } // 得到被移动的对象的指针,以便我们可以把它重新插⼊入 BNRItem *p = [allItems objectAtIndex:from]; // 从数组中删除 [allItems removeObjectAtIndex:from]; // 在新的位置重新插⼊入 [allItems insertObject:p atIndex:to];}

Page 30: 10 Editing UITableView

tableView:moveRowAtIndexPath:toIndexPath

•在 ItemsViewController.m 中实现 tableView:moveRowAtIndexPath:toIndexPath: 来更新 store

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{ [[BNRItemStore defaultStore] moveItemAtIndex:[sourceIndexPath row] toIndex:[destinationIndexPath row]];}

Page 31: 10 Editing UITableView

移动⼀一⾏行•只要简单的实现

tableView:moveRowAtIndexPath:toIndexPath: 就可以让重新排序的控件出现,这是因为 Objective-C 语⾔言的特性

•UITableView 可以在运⾏行时询问它的数据源是否实现了这个⽅方法