iOS TableView 编程指导(六)- row和section的插入和删除

我们都知道tableView有个编辑模式, 该模式下, tableView中的每一行会显示编辑控件和排序控件. 通过这些控件你可以对cell删除和排序, 或者增加一个新的cell. 这些control的外观是独特的, 如下:


Deletion control

Insertion control

当TableView进入编辑模式后, 用户点击了编辑控件, TableView会发送一系列消息给到DataSource和delegate
, 但是只有当你实现了这些方法才会. 这些方法可以让DataSource和delegate可以改进row的外观和行为; 另外通过这些方法可以让他们执行删除和插入操作.

即使tableView处于正常状态, 你也可以对cell的删除和插入, 也可删除section, 这些操作都可以动画展示.

下面内容的第一部分告诉你如何对编辑模式下的TableView中的cell进行编辑, 第二部分告诉你如何同时删除多个cell或section.

在编辑模式下对行的删除和插入


TableView的编辑模式

当TableView进入编辑状态时, tableView会受到setEditing:animated:消息. 通常(但不一定), 该消息源于用户点击导航栏编辑按钮时发送的动作消息. 可以通过delegate的tableView:editingStyleForRowAtIndexPath:方法来控制编辑模式下, row中显示编辑控件的样式.

注意:如果view controller中实现了setEditing:animated:animated:方法, 那么你可以在该方法中做一些你需要的事情, 比如更新某些button的状态, controller中的这个方法会在TableView中的setEditing:animated:animated:方法之前的被调用.

当tableView收到setEditing:animated:消息后, 它会给tableView中所有可见的cell发送相同的消息. 然后发送一系列消息给到DataSource和delegate(前提是实现了相应的方法), 这一过程如下图6-1所示.

图6-1 插入和删除cell的方法调用

当cell收到setEditing:animated:消息后, 接下来的方法调用序列如下:

  1. tableView调用DataSource中的tableView:canEditRowAtIndexPath:方法(如果已经实现). 在这个方法中可以将一些cell排除在编辑状态之外, 即使这些cell的editingStyle中的值另有指示. 大多数情况下和APP不需要实现该方法.
  2. tableView调用delegate中的tableView:editingStyleForRowAtIndexPath:方法(如果已经实现). 通过该方法可以控制cell的显示的编辑风格和编辑控件, 到了此时, tableView已经完全进入了编辑模式.
  3. 然后用户点击相应的编辑控件, 如果点击删除控件, 那么改行会展示一个删除按钮, 可以让用户确认是否真的删除该行.
  4. tableView发送tableView:commitEditingStyle:forRowAtIndexPath:给到DataSource对象. 尽管该协议方法时可选的, 但是要是实现cell的删除和插入, 你就必须实现这个方法, 另外:
    • 实现deleteRowsAtIndexPaths:withRowAnimation:或者insertRowsAtIndexPaths:withRowAnimation:来调整TableView的显示
    • 更新相应的模型数据数组, 往数组删除相应的数据或者添加相应的数据.

当用轻扫cell来展示删除按钮时, 在此过程中方法调用和上图6-1所示有点不同. 当用轻扫cell来删除时, TableView会先检查DataSource是否实现了tableView:commitEditingStyle:forRowAtIndexPath:方法; 如果实现了该方法, TableView会发送setEditing:animated:消息给自己后进入编辑模式. 在"swipe to delete"这种模式下, TableView不会展示添加/移动等控件. 因为该事件是由用户驱动的, TableView会调用delegate的两个方法tableView:willBeginEditingRowAtIndexPath:tableView:didEndEditingRowAtIndexPath:. 通过实现这两个方法, 可以适当地更新TableView的外观.

注意:在DataSource的tableView:commitEditingStyle:forRowAtIndexPath:方法中, 记得不要调用setEditing:animated:方法, 如果因为某些原因非得调用, 那么可以使用performSelector:withObject:afterDelay:来延迟调用该方法.

虽然你可以通过cell中的插入控件来插入新的cell, 当这里有种替代方法, 就是在导航栏中添加一个"Add"按钮, 点击该按钮发送一个action给到viewController, 然后modal出一个新view, 用来给用户输入新的item, 该view会遮住TableView. 当新的item输入完成后, viewController会更新data, 然后reload TableView. 下面的内容会提到该方法.

删除cell的一个例子

该部分内容通过一个例子向你展示TableView的搭建和处理TableView编辑相关的实现. 该demo使用导航栏的控制器结构来管理TableView. 在loadView方法中, 创建TableView和设置DataSource和delegate, 然后将导航栏的右边的barButton设置为edit button.

self.navigationItem.rightBarButtonItem = self.editButtonItem;

该按钮已经设置好了点击的action为发送setEditing:animated:消息给到view controller; button的title会在"Edit"和"Done"来回切换. 代码6-1展示了该方法的实现.
代码清单6-1 view controller对setEditing:animated:方法的反应

- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
    [super setEditing:editing animated:animated];
    [tableView setEditing:editing animated:YES];
    if (editing) {
        addButton.enabled = NO;
    } else {
        addButton.enabled = YES;
    }
}

当TableView进入编辑模式下, viewController会让所有的cell显示一个删除控件, 除了最后一个cell外. 最后一个cell会显示一个插入控件. 这些是通过tableView:editingStyleForRowAtIndexPath:实现的, 如代码6-2.
代码清单6-2 设置行的编辑风格

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
    SimpleEditableListAppDelegate *controller = (SimpleEditableListAppDelegate *)[[UIApplication sharedApplication] delegate];
    if (indexPath.row == [controller countOfList]-1) {
        return UITableViewCellEditingStyleInsert;
    } else {
        return UITableViewCellEditingStyleDelete;
    }
}

当用户点击行中的delete控件时, TableView会调用viewController中的tableView:commitEditingStyle:forRowAtIndexPath:方法, 如代码6-3所示, 在该方法的实现是删除相应的数据项, 然后给tableView发送一个deleteRowsAtIndexPaths:withRowAnimation:消息.
代码清单6-3 更新数据源和删除相应的行

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    // 如果此行被删除, 那么将其从列表中移除.
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        SimpleEditableListAppDelegate *controller = (SimpleEditableListAppDelegate *)[[UIApplication sharedApplication] delegate];
        [controller removeObjectFromListAtIndex:indexPath.row];
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    }
}

添加一个cell的例子

这部分内容告诉你如何往tableView中添加cell. 在本例中, 不是通过cell上的插入控件来引发添加操作, 而是通过在导航栏上添加一个"Add"button来开启添加操作. 下面代码6-4, 展示了viewController的loadView实现, 将addButton设置为导航栏的右侧barButton.
代码清单6-4 往导航栏中添加一个Add button

addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addItem:)];
self.navigationItem.rightBarButtonItem = addButton;

注意控制器还需要设置控件的标题和控件action的selector. 当用户点击Add按钮时会调用addItem:方法. 该action方法的实现如6-5所示. 在该方法中, 会创建一个包含一个viewController的navigationController, 然后将其modal出来.
代码清单6-5 Add按钮的action方法

- (void)addItem:sender {
    if (itemInputController == nil) {
        itemInputController = [[ItemInputController alloc] init];
    }
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:itemInputController];
    [[self navigationController] presentModalViewController:navigationController animated:YES];
}

在新modal出的controller中, 有个textfield和一个save按钮, 用户输入新的内容后, 点击save按钮会调动controller中的save:方法, 如下代码6-6所示, viewController会从textfield提取用户输入的信息, 然后使用该信息去更新tableView的data数组.
代码清单6-6 往数据模型数组中添加一个新item

- (void)save:sender {
    UITextField *textField = [(EditableTableViewTextField *)[tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]] textField];
 
    SimpleEditableListAppDelegate *controller = (SimpleEditableListAppDelegate *)[[UIApplication sharedApplication] delegate];
    NSString *newItem = textField.text;
    if (newItem != nil) {
            [controller insertObject:newItem inListAtIndex:[controller countOfList]];
    }
    [self dismissModalViewControllerAnimated:YES];
}

当modal出的view被dismiss后, TableView会被reloaded, 之后新添加的item会被显示出来.

批量插入/删除/排序tableView中的行和组


UITableView类允许你在同一时间将一组行或section插入/删除, 并且以动画展示. 在代码6-7中, 有8个方法用来批量删除和插入. 注意, 你可以在animation-block的外面调用这些删除和插入方法(就如前面内容中所述的DataSource tableView:commitEditingStyle:forRowAtIndexPath:方法实现一样).
代码清单6-7 删除/插入的批处理方法

- (void)beginUpdates;
- (void)endUpdates;
 
- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
 
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation: (UITableViewRowAnimation)animation;
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation: (UITableViewRowAnimation)animation;
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;

注意:reloadSections:withRowAnimation:reloadRowsAtIndexPaths:withRowAnimation:这两个方法是在iOS3.0引入的, 这些方法可以让tableView重载具体的sections和rows, 而不是调用reloadData来更新整体可见的tableView.

为了动画展示批量插入/删除/重排序, 你需要在beginUpdatesendUpdates两个方法调用之间调用前面的批处理方法. 如果你不这样做, 调用批处理方法后, row和section的index可能会变的无效了. 另外beginUpdatesendUpdates这两个方法可以嵌套使用.

总而言之, 在调用endUpdates方法返回后, tableView回去访问DataSource和delegate对象, 请求行和数据. 所以在删除和插入时应该更新tableView背景的数据模型数组.

批量删除和插入的例子

为了批量插入或者删除一组行/组, 第一是准备好数组用来存放rows和sections背后的数据. 当行和组被插入和删除后, 从该数组中获取数据来填充行和组.
接下来是, 调用beginUpdates方法, 然后是调用insertRowsAtIndexPaths:withRowAnimation:, deleteRowsAtIndexPaths:withRowAnimation:, insertSections:withRowAnimation:, 或者insertSections:withRowAnimation:, 然后使用endUpdates方法来提交上述操作. 代码6-8展示了这一操作.
代码清单6-8 批量删除/插入操作

- (IBAction)insertAndDeleteRows:(id)sender {
    // original rows: Arizona, California, Delaware, New Jersey, Washington
 
    [states removeObjectAtIndex:4]; // Washington
    [states removeObjectAtIndex:2]; // Delaware
    [states insertObject:@"Alaska" atIndex:0];
    [states insertObject:@"Georgia" atIndex:3];
    [states insertObject:@"Virginia" atIndex:5];
 
    NSArray *deleteIndexPaths = [NSArray arrayWithObjects:
                                [NSIndexPath indexPathForRow:2 inSection:0],
                                [NSIndexPath indexPathForRow:4 inSection:0],
                                nil];
    NSArray *insertIndexPaths = [NSArray arrayWithObjects:
                                [NSIndexPath indexPathForRow:0 inSection:0],
                                [NSIndexPath indexPathForRow:3 inSection:0],
                                [NSIndexPath indexPathForRow:5 inSection:0],
                                nil];
    UITableView *tv = (UITableView *)self.view;
 
    [tv beginUpdates];
    [tv insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationRight];
    [tv deleteRowsAtIndexPaths:deleteIndexPaths withRowAnimation:UITableViewRowAnimationFade];
    [tv endUpdates];
 
    // ending rows: Alaska, Arizona, California, Georgia, New Jersey, Virginia
}

排序操作和Index Paths

你可能注意到了代码6-8中deleteRowsAtIndexPaths:withRowAnimation:方法要在insertRowsAtIndexPaths:withRowAnimation:方法后. 但是tableView执行的操作不一定是按照block中的方法调用顺序. 事实上, TableView会将所有的插入操作延后, 直到处理完删除操作. TableView的行为与更新块内调用的重新加载方法相同——在执行动画块之前,对行和节的索引进行重新加载。这种行为和插入/删除/排序的顺序无关.

删除和热loading操作会决定原TableView中的那些行/组需要删除或者更新位置; 插入操作会指定向TableView中加入那些行/组. IndexPath用来标记row和section的位置, 另一方面,插入或移除可变数组中的项可能会影响用于连续插入或移除操作的数组索引;例如,如果在某个索引处插入项,则数组中所有后续项的索引都会递增。

这里有个例子, 假设你有个TableView, 其中有三个section, 每个section中有三个row, 然后实现下面的动画块:

  1. Begin updates
  2. 删除索引为(row:1, section:0)处的行
  3. 删除索引为(section:1)处的组
  4. 往索引(row:1, section:1)处插入一行
  5. End updates

图6-2, 展示了该动画块执行的过程


图6-2 删除行和组并插入行
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容