10 Editing UITableView
-
Upload
tom-fan -
Category
Technology
-
view
328 -
download
0
description
Transcript of 10 Editing UITableView
编辑 UITableView
范圣刚,[email protected], www.tfan.org
编辑模式下的 Homepwner
•上⼀一节我们实现了把 BNRItem 的⼀一系列实例在 UITableView 中显⽰示的应⽤用
•下⼀一步我们将允许⽤用户和表格进⾏行交互 - 添加,删除和移动表格的⾏行
编辑模式
•UITableView 有⼀一个 editing 属性,并且当这个属性被设置成 YES 的时候, UITableView 就进⼊入了编辑模式。
•⼀一旦 table view 进⼊入编辑模式,表格中的⾏行就可以被⽤用户操作。⽤用户可以改变⾏行的顺序,添加新⾏行,或者删除⾏行。但是编辑模式并不允许⽤用户编辑表格⾏行本⾝身的内容
•怎么才能让⽤用户进⼊入编辑模式?我们将使⽤用在表格的 header view 中的⼀一个按钮来切换编辑模式
header view
• header view 出现在表格的⼀一个 section 的顶部,被⽤用来增加 section 范围或者 table 范围的标题和控件
•可以是任意 UIView 的实例
•同样也有在 section 底部的 footer view,原理是⼀一样的
我们⽤用到的 header view
•我们要创建的 header view 将会出现在 BNRItem 列表的顶部,将会包含两个⼦子视图,都是 UIButton 的实例•⼀一个⽤用来切换编辑模式•另⼀一个⽤用来往表格中增加⼀一个新的 BNRItem
•我们将在 XIB ⽂文件中创建这个视图,然后 ItemsViewController 将在其需要显⽰示这个 header view 的时候解压这个 XIB ⽂文件
headerView 相关的变量和⽅方法•⾸首先我们来设置必要的代码•在 ItemsViewController.h 中为我们的 header view 声明⼀一个 UIView 类型的实例变量和三个新的⽅方法
@interface ItemsViewController : UITableViewController{ IBOutlet UIView *headerView;}
- (UIView *)headerView;- (IBAction)addNewItem:(id)sender;- (IBAction)toggleEdittingMode:(id)sender;
XIB ⽂文件:HeaderView
•新的 XIB ⽂文件(File -> New -> File, iOS -> User Interface, Emtpy, 保存为: HeaderView)
•和我们之前创建的所有 XIB ⽂文件都不同,这个 XIB ⽂文件和 view controller 的 view 没有任何关系
•作为 UITableViewController 的⼦子类,ItemsViewController 总是知道如何⽣生成它的 view
• XIB ⽂文件通常被⽤用来为⼀一个 view controller ⽣生成 view,但是我们也可以在任何想要布局 view 对象的时候使⽤用它
File’s Owner 的类•⾸首先在 HeaderView.xib 中,选择 File’s Owner 对象,然后把它的 Class 改成 ItemsViewController
•然后拖曳⼀一个 UIView 到 canvas 区域,再拖曳两个 UIButton 的实例到这个视图上
•调整这个 UIView 的⼤大⼩小并按下图⽣生成连接
HeaderView 的布局和连接
view 的背景颜⾊色•同时要把 UIView 实例的颜⾊色改成完全透明
•在 view 的 attribute inspector 中,点击 Background 的颜⾊色选择器,然后把 Opacity 拖到 0(clear color)
加载 HeaderView.xib
•截⾄至⺫⽬目前,XIB ⽂文件的加载都是由 UIViewController 的实现⾃自动完成的
•⽐比如前⾯面的 TimeViewController 知道如何加载 TimeViewController.xib, 因为写在它的超类(UIViewController)中的代码
•对于 HeaderView.xib, 我们⼿手动来写⼀一些代码让 ItemsViewController 加载这个 XIB ⽂文件
•要⼿手动加载 XIB ⽂文件,我们要⽤用到 NSBundle
NSBundle 和 mainBundle
•NSBundle 类是 application 和 application bundle 之间的接⼝口
•当我们想要访问位于 application bundle 中的⽂文件时,我们向 NSBundle 请求它
•当应⽤用程序启动时,⼀一个 NSBundle 的实例就被创建,然后我们可以通过向 NSBundle 发送 mainBundle 消息获得指向这个实例的指针
•⼀一旦我们拿到了指向 main bundle 对象的指针,我们就可以请求它加载⼀一个 XIB ⽂文件
实现 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;}
两个 header view 相关的⽅方法•我们已经⽣生成了 headerView, 下⾯面就使它成为
table 的 header view。这需要在 ItemsViewController.m 中从 UITableViewDelegate protocol 实现两个⽅方法• tableView:viewForHeaderInSection:, 返回⼀一个 view
• tableView:heightForHeaderInSection:
•虽然这两个⽅方法在 protocol 中都是作为 optional 列出来的,但是想要 header view 的话,必须实现他们
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;}
editing 属性• headerView 已经可以成为 table 的 header view 了,下⾯面来实现 toggleEditingMode: ⽅方法,也就是我们点击按钮切换编辑状态的⽅方法
•我们可以直接切换 UITableView 的 editing 属性。然⽽而,UITableViewController 也有⼀一个 editing 属性。⼀一个 UITableViewController 实例会⾃自动设置它的 table view 的 editing 属性来和它的 editing 属性相匹配。该⽤用哪⼀一个?遵循 MVC 模式:和 controller 打交道,让 controller 去和 view 打交道
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]; }}
Editing mode 下的 UITableView
增加⾏行•我们使⽤用 header view 中的⼀一个“新建”按钮来给表格增加新⾏行,当按钮被按下时,⼀一个新⾏行将被增加到 UITableView。
•是 UITableView 的 dataSource 来决定 table view 要显⽰示的⾏行数,因此我们要确保 UITableView 显⽰示的⾏行数要和 dataSource 保存的⾏行数⼀一致
•因此我们在实现 addNewItem: 时,在往 table view 插⼊入⼀一⾏行前,要先把增加⼀一个新的 BNRItem 到 BNRItemStore 中
实现 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];}
删除⾏行•在删除模式下,带横线的红圈是删除控件,触控其中⼀一个应该删除那⼀一⾏行。然后,此时触控删除控件不会发⽣生任何事情。在 table view 将删除⼀一⾏行前,它会发送⼀一个有关拟删除的消息给它的数据源,并且在扣动扳机前会等待⼀一个确认消息
•在删除⼀一个单元格时,必须要做两件事情:•从 UITableView 删除那⼀一⾏行
•从 BNRItemStore 中删除和它关联的 BNRItem
• BNRItemStore 必须知道如何从其⾃自⾝身删除⼀一个对象,我们要在 BNRItemStore.h 中声明⼀一个新的⽅方法:removeItem:
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];}
UITableViewCellEditingStyleDelete
•下⾯面我们实现 tableView:commitEditingStyle:forRowAtIndexPath:, ⼀一个来⾃自 UITableViewDataSource protocol 的⽅方法。
•当 tableView:commitEditingStyle:forRowAtIndexPath: 被发送给数据源时,两个额外的参数也被随着传递•第⼀一个是 UITableViewCellEditingStyle, 在这⾥里是
UITableViewCellEditingStyleDelete
•另⼀一个参数是表格中⾏行的 NSIndexPath
实现确认删除的⽅方法
•在 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]; }}
移动⾏行
•要改变⼀一个 UITableView 中⾏行的顺序,要使⽤用来⾃自 UITableViewDataSource 的另外⼀一个⽅方法:tableView:moveRowAtIndexPath:toIndexPath:
•前⾯面我们看到要删除⼀一⾏行的话,要发送 deleteRowsAtIndexPaths:withRowAnimation: 给 UITableView 来确认删除
•移动⼀一⾏行的话,不需要确认,只是通过发送 tableView:moveRowAtIndexPath:toIndexPath: 消息给它的数据源通知这个移动操作
•我们只需要捕捉这个消息来更新我们的数据源以匹配新的顺序
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];}
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]];}
移动⼀一⾏行•只要简单的实现
tableView:moveRowAtIndexPath:toIndexPath: 就可以让重新排序的控件出现,这是因为 Objective-C 语⾔言的特性
•UITableView 可以在运⾏行时询问它的数据源是否实现了这个⽅方法