编辑 UITableView




范圣刚,princetoad@gmail.com, 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 ⽅方法
- (UIView *)headerView
{
    // 如果还没有加载 headerView ...
    if (!headerView) {
        // 加载 HeaderView.xib
        [[NSBundle mainBundle] loadNibNamed:@"HeaderView" owner:self options:nil];
    }

    return headerView;
}

    • ⾸首先注意⼀一下 loadNibNamed:owner:options: ⽅方法的参数
       • 不需要加 XIB ⽂文件的扩展名,NSBundle 会⾃自⼰己搞定
       • 我们是把 self 作为 XIB ⽂文件的 owner 传进去的,这就把
         ItemsViewController 的实例填到了 XIB ⽂文件的 File’s Owner hole 了

    • headerView 消息第⼀一次被发到 ItemViewController 时,它就会
       加载 HeaderView.xib 并且在实例变量 headerView 中保存⼀一个
       指向 view 对象的指针。当 view 上的按钮被按下时就会发消
       息到 ItemViewController
两个 header view 相关的⽅方法
• 我们已经⽣生成了 headerView, 下⾯面就使它成为
 table 的 header view。这需要在
 ItemsViewController.m 中从 UITableViewDelegate
 protocol 实现两个⽅方法
 • tableView:viewForHeaderInSection:, 返回⼀一个 view
 • tableView:heightForHeaderInSection:
• 虽然这两个⽅方法在 protocol 中都是作为 optional
 列出来的,但是想要 header view 的话,必须实现
 他们
headerView 的加载
// 实现两个⽅方法使 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;
}


 • 实现着两个⽅方法之后,当 UITableView 需要显⽰示它的 header view
    时,就会发送这些消息给它的 delegate,也就是 ItemsViewController
 • 当 tableView:heightForHeaderInSection: 消息第⼀一次被发给
    ItemsViewController 时,它会先发给⾃自⼰己⼀一个 headerView 消息。此
    时,headerView 还是 nil,这将会引起 headerView 被从 XIB ⽂文件中被
    加载
editing 属性
• headerView 已经可以成为 table 的 header view
 了,下⾯面来实现 toggleEditingMode: ⽅方法,也就是
 我们点击按钮切换编辑状态的⽅方法
• 我们可以直接切换 UITableView 的 editing 属性。
 然⽽而,UITableViewController 也有⼀一个 editing 属
 性。⼀一个 UITableViewController 实例会⾃自动设置
 它的 table view 的 editing 属性来和它的 editing 属
 性相匹配。该⽤用哪⼀一个?遵循 MVC 模式:和
 controller 打交道,让 controller 去和 view 打交道
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];
    }
}


 • 要设置 view controller 的 editing 属性,就给它发
   送 setEditing:animated: 消息
Editing mode 下的 UITableView
增加⾏行
• 我们使⽤用 header view 中的⼀一个“新建”按钮来给表
 格增加新⾏行,当按钮被按下时,⼀一个新⾏行将被增
 加到 UITableView。
• 是 UITableView 的 dataSource 来决定 table view 要
 显⽰示的⾏行数,因此我们要确保 UITableView 显⽰示的
 ⾏行数要和 dataSource 保存的⾏行数⼀一致
• 因此我们在实现 addNewItem: 时,在往 table view
 插⼊入⼀一⾏行前,要先把增加⼀一个新的 BNRItem 到
 BNRItemStore 中
实现 addNewItem:
- (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];
}


 • 发送 tableView 消息给 UITableViewController 将返
   回 controller 的 table view
 • 现在我们可以⾃自⼰己添加新⾏行了,可以把 init ⽅方法
   中往 store 中压⼊入 5 个随机 items 的代码删掉了
删除⾏行
• 在删除模式下,带横线的红圈是删除控件,触控
 其中⼀一个应该删除那⼀一⾏行。然后,此时触控删除
 控件不会发⽣生任何事情。在 table view 将删除⼀一
 ⾏行前,它会发送⼀一个有关拟删除的消息给它的数
 据源,并且在扣动扳机前会等待⼀一个确认消息
• 在删除⼀一个单元格时,必须要做两件事情:
 • 从 UITableView 删除那⼀一⾏行
 • 从 BNRItemStore 中删除和它关联的 BNRItem
• BNRItemStore 必须知道如何从其⾃自⾝身删除⼀一个对
 象,我们要在 BNRItemStore.h 中声明⼀一个新的⽅方
 法:removeItem:
removeItem:
@interface BNRItemStore : NSObject
{
    NSMutableArray *allItems;
}

+ (BNRItemStore *)defaultStore;

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

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

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


 • 可以使⽤用 removeObject: ⽅方法替换 removeObjectIdenticalTo: ,但是
   要注意它们的区别
 • removeObject: ⽅方法遍历数组的每个对象并发送给它们⼀一个 isEqual:
   消息。类可以实现这个⽅方法根据⾃自⼰己的判断来返回 YES or NO
UITableViewCellEditingStyleDelete
• 下⾯面我们实现
 tableView:commitEditingStyle:forRowAtIndexPath:, ⼀一
 个来⾃自 UITableViewDataSource protocol 的⽅方法。
•当
 tableView:commitEditingStyle:forRowAtIndexPath:
 被发送给数据源时,两个额外的参数也被随着传
 递
 • 第⼀一个是 UITableViewCellEditingStyle, 在这⾥里是
  UITableViewCellEditingStyleDelete

 • 另⼀一个参数是表格中⾏行的 NSIndexPath
实现确认删除的⽅方法
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(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];
    }
}


      • 在 ItemsViewController.m 中实现这个⽅方法
        • 让 BNRItemStore 删除正确的对象
        • 通过发回给table view
           deleteRowsAtIndexPaths:withRowAnimation: 消息确认对
           ⾏行的删除
移动⾏行
• 要改变⼀一个 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:moveRowAtI
 ndexPath:toIndexPath:
 就可以让重新排序的
 控件出现,这是因为
 Objective-C 语⾔言的特
 性

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

10 Editing UITableView

  • 1.
  • 2.
    编辑模式下的 Homepwner • 上⼀一节我们实现了把BNRItem 的⼀一系列实例在 UITableView 中显⽰示的应⽤用 • 下⼀一步我们将允许⽤用户和表格进⾏行交互 - 添加, 删除和移动表格的⾏行
  • 3.
  • 4.
    • UITableView 有⼀一个editing 属性,并且当这个属性 被设置成 YES 的时候, UITableView 就进⼊入了编辑 模式。 • ⼀一旦 table view 进⼊入编辑模式,表格中的⾏行就可 以被⽤用户操作。⽤用户可以改变⾏行的顺序,添加新 ⾏行,或者删除⾏行。但是编辑模式并不允许⽤用户编 辑表格⾏行本⾝身的内容 • 怎么才能让⽤用户进⼊入编辑模式?我们将使⽤用在表 格的 header view 中的⼀一个按钮来切换编辑模式
  • 5.
    header view • headerview 出现在表 格的⼀一个 section 的顶 部,被⽤用来增加 section 范围或者 table 范围的标题和控件 • 可以是任意 UIView 的 实例 • 同样也有在 section 底 部的 footer view,原 理是⼀一样的
  • 6.
    我们⽤用到的 header view •我们要创建的 header view 将会出现在 BNRItem 列 表的顶部,将会包含两个⼦子视图,都是 UIButton 的实例 • ⼀一个⽤用来切换编辑模式 • 另⼀一个⽤用来往表格中增加⼀一个新的 BNRItem • 我们将在 XIB ⽂文件中创建这个视图,然后 ItemsViewController 将在其需要显⽰示这个 header view 的时候解压这个 XIB ⽂文件
  • 7.
    headerView 相关的变量和⽅方法 •⾸首先我们来设置必要的代码 • 在 ItemsViewController.h 中为我们的 header view 声明⼀一个 UIView 类型的实例变量和三个新的⽅方法 @interface ItemsViewController : UITableViewController { IBOutlet UIView *headerView; } - (UIView *)headerView; - (IBAction)addNewItem:(id)sender; - (IBAction)toggleEdittingMode:(id)sender;
  • 8.
    XIB ⽂文件:HeaderView • 新的XIB ⽂文件(File -> New -> File, iOS -> User Interface, Emtpy, 保存为: HeaderView) • 和我们之前创建的所有 XIB ⽂文件都不同,这个 XIB ⽂文件和 view controller 的 view 没有任何关系 • 作为 UITableViewController 的⼦子类, ItemsViewController 总是知道如何⽣生成它的 view • XIB ⽂文件通常被⽤用来为⼀一个 view controller ⽣生成 view,但是我们也可以在任何想要布局 view 对象 的时候使⽤用它
  • 9.
    File’s Owner 的类 •⾸首先在 HeaderView.xib 中,选择 File’s Owner 对 象,然后把它的 Class 改 成 ItemsViewController • 然后拖曳⼀一个 UIView 到 canvas 区域,再拖曳两个 UIButton 的实例到这个视 图上 • 调整这个 UIView 的⼤大⼩小并 按下图⽣生成连接
  • 10.
  • 11.
    view 的背景颜⾊色 • 同时要把UIView 实例 的颜⾊色改成完全透明 • 在 view 的 attribute inspector 中,点击 Background 的颜⾊色选 择器,然后把 Opacity 拖到 0(clear color)
  • 12.
    加载 HeaderView.xib • 截⾄至⺫⽬目前,XIB⽂文件的加载都是由 UIViewController 的实现⾃自动完成的 • ⽐比如前⾯面的 TimeViewController 知道如何加载 TimeViewController.xib, 因为写在它的超类 (UIViewController)中的代码 • 对于 HeaderView.xib, 我们⼿手动来写⼀一些代码让 ItemsViewController 加载这个 XIB ⽂文件 • 要⼿手动加载 XIB ⽂文件,我们要⽤用到 NSBundle
  • 13.
    NSBundle 和 mainBundle •NSBundle 类是 application 和 application bundle 之间的接⼝口 • 当我们想要访问位于 application bundle 中的⽂文件 时,我们向 NSBundle 请求它 • 当应⽤用程序启动时,⼀一个 NSBundle 的实例就被创 建,然后我们可以通过向 NSBundle 发送 mainBundle 消息获得指向这个实例的指针 • ⼀一旦我们拿到了指向 main bundle 对象的指针, 我们就可以请求它加载⼀一个 XIB ⽂文件
  • 14.
    实现 headerView ⽅方法 -(UIView *)headerView { // 如果还没有加载 headerView ... if (!headerView) { // 加载 HeaderView.xib [[NSBundle mainBundle] loadNibNamed:@"HeaderView" owner:self options:nil]; } return headerView; } • ⾸首先注意⼀一下 loadNibNamed:owner:options: ⽅方法的参数 • 不需要加 XIB ⽂文件的扩展名,NSBundle 会⾃自⼰己搞定 • 我们是把 self 作为 XIB ⽂文件的 owner 传进去的,这就把 ItemsViewController 的实例填到了 XIB ⽂文件的 File’s Owner hole 了 • headerView 消息第⼀一次被发到 ItemViewController 时,它就会 加载 HeaderView.xib 并且在实例变量 headerView 中保存⼀一个 指向 view 对象的指针。当 view 上的按钮被按下时就会发消 息到 ItemViewController
  • 15.
    两个 header view相关的⽅方法 • 我们已经⽣生成了 headerView, 下⾯面就使它成为 table 的 header view。这需要在 ItemsViewController.m 中从 UITableViewDelegate protocol 实现两个⽅方法 • tableView:viewForHeaderInSection:, 返回⼀一个 view • tableView:heightForHeaderInSection: • 虽然这两个⽅方法在 protocol 中都是作为 optional 列出来的,但是想要 header view 的话,必须实现 他们
  • 16.
    headerView 的加载 // 实现两个⽅方法使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; } • 实现着两个⽅方法之后,当 UITableView 需要显⽰示它的 header view 时,就会发送这些消息给它的 delegate,也就是 ItemsViewController • 当 tableView:heightForHeaderInSection: 消息第⼀一次被发给 ItemsViewController 时,它会先发给⾃自⼰己⼀一个 headerView 消息。此 时,headerView 还是 nil,这将会引起 headerView 被从 XIB ⽂文件中被 加载
  • 17.
    editing 属性 • headerView已经可以成为 table 的 header view 了,下⾯面来实现 toggleEditingMode: ⽅方法,也就是 我们点击按钮切换编辑状态的⽅方法 • 我们可以直接切换 UITableView 的 editing 属性。 然⽽而,UITableViewController 也有⼀一个 editing 属 性。⼀一个 UITableViewController 实例会⾃自动设置 它的 table view 的 editing 属性来和它的 editing 属 性相匹配。该⽤用哪⼀一个?遵循 MVC 模式:和 controller 打交道,让 controller 去和 view 打交道
  • 18.
    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]; } } • 要设置 view controller 的 editing 属性,就给它发 送 setEditing:animated: 消息
  • 19.
  • 20.
    增加⾏行 • 我们使⽤用 headerview 中的⼀一个“新建”按钮来给表 格增加新⾏行,当按钮被按下时,⼀一个新⾏行将被增 加到 UITableView。 • 是 UITableView 的 dataSource 来决定 table view 要 显⽰示的⾏行数,因此我们要确保 UITableView 显⽰示的 ⾏行数要和 dataSource 保存的⾏行数⼀一致 • 因此我们在实现 addNewItem: 时,在往 table view 插⼊入⼀一⾏行前,要先把增加⼀一个新的 BNRItem 到 BNRItemStore 中
  • 21.
    实现 addNewItem: - (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]; } • 发送 tableView 消息给 UITableViewController 将返 回 controller 的 table view • 现在我们可以⾃自⼰己添加新⾏行了,可以把 init ⽅方法 中往 store 中压⼊入 5 个随机 items 的代码删掉了
  • 22.
    删除⾏行 • 在删除模式下,带横线的红圈是删除控件,触控 其中⼀一个应该删除那⼀一⾏行。然后,此时触控删除 控件不会发⽣生任何事情。在 table view 将删除⼀一 ⾏行前,它会发送⼀一个有关拟删除的消息给它的数 据源,并且在扣动扳机前会等待⼀一个确认消息
  • 23.
    • 在删除⼀一个单元格时,必须要做两件事情: •从 UITableView 删除那⼀一⾏行 • 从 BNRItemStore 中删除和它关联的 BNRItem • BNRItemStore 必须知道如何从其⾃自⾝身删除⼀一个对 象,我们要在 BNRItemStore.h 中声明⼀一个新的⽅方 法:removeItem:
  • 24.
    removeItem: @interface BNRItemStore :NSObject { NSMutableArray *allItems; } + (BNRItemStore *)defaultStore; - (NSArray *)allItems; - (BNRItem *)createItem; - (void)removeItem:(BNRItem *)p; - (void)removeItem:(BNRItem *)p { [allItems removeObjectIdenticalTo:p]; } • 可以使⽤用 removeObject: ⽅方法替换 removeObjectIdenticalTo: ,但是 要注意它们的区别 • removeObject: ⽅方法遍历数组的每个对象并发送给它们⼀一个 isEqual: 消息。类可以实现这个⽅方法根据⾃自⼰己的判断来返回 YES or NO
  • 25.
    UITableViewCellEditingStyleDelete • 下⾯面我们实现 tableView:commitEditingStyle:forRowAtIndexPath:,⼀一 个来⾃自 UITableViewDataSource protocol 的⽅方法。 •当 tableView:commitEditingStyle:forRowAtIndexPath: 被发送给数据源时,两个额外的参数也被随着传 递 • 第⼀一个是 UITableViewCellEditingStyle, 在这⾥里是 UITableViewCellEditingStyleDelete • 另⼀一个参数是表格中⾏行的 NSIndexPath
  • 26.
    实现确认删除的⽅方法 - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyleforRowAtIndexPath: (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]; } } • 在 ItemsViewController.m 中实现这个⽅方法 • 让 BNRItemStore 删除正确的对象 • 通过发回给table view deleteRowsAtIndexPaths:withRowAnimation: 消息确认对 ⾏行的删除
  • 27.
  • 28.
    • 要改变⼀一个 UITableView中⾏行的顺序,要使⽤用来⾃自 UITableViewDataSource 的另外⼀一个⽅方法: tableView:moveRowAtIndexPath:toIndexPath: • 前⾯面我们看到要删除⼀一⾏行的话,要发送 deleteRowsAtIndexPaths:withRowAnimation: 给 UITableView 来确认删除 • 移动⼀一⾏行的话,不需要确认,只是通过发送 tableView:moveRowAtIndexPath:toIndexPath: 消息 给它的数据源通知这个移动操作 • 我们只需要捕捉这个消息来更新我们的数据源以 匹配新的顺序
  • 29.
    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]; }
  • 30.
    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]]; }
  • 31.
    移动⼀一⾏行 • 只要简单的实现 tableView:moveRowAtI ndexPath:toIndexPath: 就可以让重新排序的 控件出现,这是因为 Objective-C 语⾔言的特 性 • UITableView 可以在运 ⾏行时询问它的数据源 是否实现了这个⽅方法