Inserting and Deleting Rows and Sections

A table view has an editing mode as well as its normal (selection) mode. When a table view goes into editing mode, it displays the editing and reordering controls associated with its rows. The editing controls, which are in the left side of the row, allow the user to insert and delete rows in the table view. The editing controls have distinctive appearances:

deletion control

Deletion control

insertion control

Insertion control

When a table view enters editing mode and when users click an editing control, the table view sends a series of messages to its data source and delegate, but only if they implement these methods. These methods allow the data source and delegate to refine the appearance and behavior of rows in the table view; the messages also enable them to carry out the deletion or insertion operation.

Even if a table view is not in editing mode, you can insert or delete a number of rows or sections as a group and have those operations animated.

The first section below shows you how, when a table is in editing mode, to insert new rows and delete existing rows in a table view in response to user actions. The second section, “Batch Insertion, Deletion, and Reloading of Rows and Sections,” discusses how you can insert and delete multiple sections and rows animated as a group.

Inserting and Deleting Rows in Editing Mode

When a Table View is Edited

A table view goes into editing mode when it receives a setEditing:animated: message. Typically (but not necessarily) the message originates as an action message sent when the user taps an Edit button in the navigation bar. In editing mode, a table view displays any editing (and reordering) controls that its delegate has assigned to each row. The delegate assigns the controls as a result of returning the editing style for a row in the tableView:editingStyleForRowAtIndexPath: method.

When the table view receives setEditing:animated:, it sends the same message to the UITableViewCell object for each visible row. Then it sends a succession of messages to its data source and its delegate (if they implement the methods) as depicted in the diagram in Figure 7-1.

Figure 7-1  Calling sequence for inserting or deleting rows in a table view
Calling sequence for inserting or deleting rows in a table view

After resending setEditing:animated: to the cells corresponding to the visible rows, the sequence of messages is as follows:

  1. The table view invokes the tableView:canEditRowAtIndexPath: method if its data source implements it. This method allows the application to exclude rows in the table view from being edited even when their cell’s editingStyle property indicates otherwise. Most applications do not need to implement this method.

  2. The table view invokes the tableView:editingStyleForRowAtIndexPath: method if its delegate implements it. This method allows the application to specify a row’s editing style and thus the editing control that the row displays.

    At this point, the table view is fully in editing mode. It displays the insertion or deletion control for each eligible row.

  3. The user taps an editing control (either the deletion control or the insertion control). If he or she taps a deletion control, a Delete button is displayed on the row. The user then taps that button to confirm the deletion.

  4. The table view sends the tableView:commitEditingStyle:forRowAtIndexPath: message to the data source. Although this protocol method is marked as optional, the data source must implement it if it wants to insert or delete a row. It must do two things:

When the user swipes across a row to display the Delete button for that row, there is a variation in the calling sequence diagrammed in Figure 7-1. When the user swipes a row to delete it, the table view first checks to see if its data source has implemented the tableView:commitEditingStyle:forRowAtIndexPath: method; if that is so, it sends setEditing:animated: to itself and enters editing mode. In this “swipe to delete” mode, the table view does not display the editing and reordering controls. Because this is a user-driven event, it also brackets the messages to the delegate inside of two other messages: tableView:willBeginEditingRowAtIndexPath: and tableView:didEndEditingRowAtIndexPath:. By implementing these methods, the delegate can update the appearance of the table view appropriately.

Although you can use an insertion control as the trigger to insert a new row in a table view, an alternative approach is to have an Add (or plus sign) button in the navigation bar. Tapping the button sends an action message to the view controller, which overlays the table view with a modal view for entering the new item. Once the item is entered, the controller adds it to the data-model array and reloads the table. “An Example of Adding a Table-View Row” discusses this approach.

An Example of Deleting a Table-View Row

This section gives a guided tour through the parts of a project that work together to set up a table view for editing mode and delete rows from it. This project uses the navigation controller and view controller architecture to manage its table views. In its loadView method, the custom view controller creates the table view and sets itself to be the data source and delegate. It also sets the right item of the navigation bar to be the standard Edit button.

self.navigationItem.rightBarButtonItem = self.editButtonItem;

This button is preconfigured to send setEditing:animated: to the view controller when tapped; it toggles the button title (between Edit and Done) and the Boolean editing parameter on alternating taps. In its implementation of the method, as shown in Listing 7-1, the view controller invokes the superclass invocation of the method, sends the same message to the table view, and updates the enabled state of the other button in the navigation bar (a plus-sign button, for adding items).

Listing 7-1  View controller responding to 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;
    }
}

When its table view enters editing mode, the view controller specifies a deletion control for every row except the last, which has an insertion control. It does this in its implementation of the tableView:editingStyleForRowAtIndexPath: method (Listing 7-2).

Listing 7-2  Customizing the editing style of rows

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

The user taps the deletion control in a row and the view controller receives a tableView:commitEditingStyle:forRowAtIndexPath: message from the table view. As shown in Listing 7-3, it handles this message by removing the item corresponding to the row from a model array and sending deleteRowsAtIndexPaths:withRowAnimation: to the table view.

Listing 7-3  Updating the data-model array and deleting the row

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    // If row is deleted, remove it from the list.
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        SimpleEditableListAppDelegate *controller = (SimpleEditableListAppDelegate *)[[UIApplication sharedApplication] delegate];
        [controller removeObjectFromListAtIndex:indexPath.row];
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    }
}

An Example of Adding a Table-View Row

This section shows project code that inserts a row in a table view. Instead of using the insertion control as the trigger for inserting a row, it uses an Add button (visually a plus sign) in the navigation bar above the table view. This code also is based on the navigation controller and view controller architecture. In its loadView method implementation, the view controller assigns the Add button as the right-side item of the navigation bar using the code shown in Listing 7-4.

Listing 7-4  Adding an Add button to the navigation bar

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

Note that the view controller sets the control states for the title as well as the action selector and the target object. When the user taps the Add button, the addItem: message is sent to the target (the view controller). It responds as shown in Listing 7-5. It creates a navigation controller with a single view controller whose view is put onscreen modally—it animates upward to overlay the table view. The presentModalViewController:animated: method to do this.

Listing 7-5  Responding to a tap on the Add button

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

The modal, or overlay, view consists of a single custom text field. The user enters text for the new table-view item and then taps a Save button. This button sends a save: action message to its target: the view controller for the modal view. As shown in Listing 7-6, the view controller extracts the string value from the text field and updates the application’s data-model array with it.

Listing 7-6  Adding the new item to the data-model array

- (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];
}

After the modal view is dismissed the table view is reloaded, and it now reflects the added item.

Batch Insertion, Deletion, and Reloading of Rows and Sections

The UITableView class allows you to insert, delete, and reload a group of rows or sections at one time, animating the operations simultaneously in specified ways. The eight methods shown in Listing 7-7 pertain to batch insertion and deletion. Note that you can call these insertion and deletion methods outside of an animation block (as you do in the data source method tableView:commitEditingStyle:forRowAtIndexPath: as discussed in “Inserting and Deleting Rows in Editing Mode”).

Listing 7-7  Batch insertion and deletion methods

- (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;

To animate a batch insertion, deletion, and reloading of rows and sections, call the corresponding methods within an animation block defined by successive calls to beginUpdates and endUpdates. If you don’t call the insertion, deletion, and reloading methods within this block, row and section indexes may be invalid. Calls to beginUpdates and endUpdates can be nested; all indexes are treated as if there were only the outer update block.

At the conclusion of a block—that is, after endUpdates returns—the table view queries its data source and delegate as usual for row and section data. Thus the collection objects backing the table view should be updated to reflect the new or removed rows or sections.

An Example of Batched Insertion and Deletion Operations

To insert and delete a group of rows and sections in a table view, first prepare the array (or arrays) that are the source of data for the sections and rows. After rows and sections are deleted and inserted, the resulting rows and sections are populated from this data store.

Next, call the beginUpdates method, followed by invocations of insertRowsAtIndexPaths:withRowAnimation:, deleteRowsAtIndexPaths:withRowAnimation:, insertSections:withRowAnimation:, or deleteSections:withRowAnimation:. Conclude the animation block by calling endUpdates. Listing 7-8 illustrates this procedure.

Listing 7-8  Inserting and deleting a block of rows in a table view

- (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
}

This example removes two strings from an array (and their corresponding rows) and inserts three strings into the array (along with their corresponding rows). The next section, “Ordering of Operations and Index Paths,” explains particular aspects of the row (or section) insertion and deletion behavior.

Ordering of Operations and Index Paths

You might have noticed something in the code shown in Listing 7-8 that seems peculiar. The code calls the deleteRowsAtIndexPaths:withRowAnimation: method after it calls insertRowsAtIndexPaths:withRowAnimation:. However, this is not the order in which UITableView completes the operations. It defers any insertions of rows or sections until after it has handled the deletions of rows or sections. The table view behaves the same way with reloading methods called inside an update block—the reload takes place with respect to the indexes of rows and sections before the animation block is executed. This behavior happens regardless of the ordering of the insertion, deletion, and reloading method calls.

Deletion and reloading operations within an animation block specify which rows and sections in the original table should be removed or reloaded; insertions specify which rows and sections should be added to the resulting table. The index paths used to identify sections and rows follow this model. Inserting or removing an item in a mutable array, on the other hand, may affect the array index used for the successive insertion or removal operation; for example, if you insert an item at a certain index, the indexes of all subsequent items in the array are incremented.

An example is useful here. Say you have a table view with three sections, each with three rows. Then you implement the following animation block:

  1. Begin updates.

  2. Delete row at index 1 of section at index 0.

  3. Delete section at index 1.

  4. Insert row at index 1 of section at index 1.

  5. End updates.

Figure 7-2 illustrates what takes place after the animation block concludes.

Figure 7-2  Deletion of section and row and insertion of row
Deletion of section and row and insertion of row