Chris Dzombak

Elegant UITableView row selection behavior based on edit state

If you're a Cocoa Touch developer, you've undoubtedly implemented -[UITableViewDelegate tableView:didSelectRowAtIndexPath:] a few hundred times. I did this recently and needed different behavior based on whether the table view was in its editing state (whether -[UITableView isEditing]). In this particular case, the best solutions I could figure out were variants of nested conditionals, which are just ugly.

Then I stumbled on something else which, depending on your use case, may be more elegant. Take a look, then I'll go into more detail. (For purposes of this example, assume CDZType is a typedef'd enum containing two values: CDZTypePhone, CDZTypeEmail.) I've added an important update about how to define this enum at the end of this post.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    CDZType rowType = [self.dataSource rowTypeAtIndexPath:indexPath];

    #define CDZTVEditing (1 << 9)
    NSUInteger rowTypeEditing = rowType | (tableView.isEditing ? CDZTVEditing : 0);

    switch (rowTypeEditing) {
        case CDZTypePhone:
            // do stuff for phone selection in non-editing state
            break;
        case CDZTypeEmail:
            // do stuff for email selection in non-editing state
            break;
        case (CDZTypePhone | CDZTVEditing):
            // do stuff for phone selection in editing state
            break;
        default: ;
    }

    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

In this case, I wanted to take some action when (a phone or email row was selected and the table view was NOT in edit mode) or (a phone row was selected AND the table view was in edit mode). This can obviously be accomplished by nesting conditionals or writing complex conditional statements, but I didn't like the style of either of those choices (and there's a lot more to this code in our app).

This solution is really simple.

I take advantage of the fact that there are very few values in this enum, and of the fact that C enum values are integers. I create a bitmask which is simply the integer 29. It seems heavy-handed, but the bitmask must be #defined; the compiler insists that it must know all values which will be used in case statements, so a static NSUInteger won't work.

If the table view is in editing mode, I bitwise-OR the row type enum with the bitmask and switch on that value; otherwise I bitwise-OR the row type with 0 (which doesn't change the value). Then I just switch on that slightly-modified integer!

A few additional notes:

Update: defining your enum

As pointed out by @peterhoneyman and @andrewa2, to avoid assuming details about the data type underlying the enum (and therefore to stay within the realm of documented behavior) you have to specify that your enum should be backed by an NSUInteger. See this Gist from Andrew for an example.

You'll notice that Apple does this in their header files wherever an enum is defined. Look up, for example, the definition of UITableViewCellStyle, and you'll notice that it's backed by an NSInteger. (You can also look at the definition of NS_ENUM in NSObjCRuntime.h to see what they're doing.)

Feedback

Feedback is welcome; comment here or contact me @cdzombak.