February 26, 2009

Radio-button behavior for table rows

One thing missing from the iPhones UI kit is a plain old radio button. There are some controls that kind of give you the exclusive selection behavior, like a tab bar or a segment control, but there is nothing like a typical RadioButton widget. The closest you can probably get using the standard UIKit framework is a table. Tables have rudimentary support for radio button behavior. When using the standard UITableCell class, it has provisions for setting the accessory type of a row to a checkmark. You have probably seen this technique in the calendar app while editing an event alert. Your choices are presented like this:
Unfortunately, this is pure visual asthetics; you still have to manage the exclusive behavior in your code. The document "Table View Programming Guide for iPhone OS " has a good example of how to implement both exclusive and inclusive selections in chapter 7 that makes use of the checkmark accessory. I won't repeat that code here, but in a nutshell, this example code handles the message tableView:didSelectRowAtIndexPath:, and what it does (for the exclusive selection behavior) is find the previously selected cell, clear it's accessory checkmark, and put the checkmark on the current cell. It's a little cumbersome, but it works. This is all fine, but what if you didn't want to use the checkmark accessory? I have an application where I need to use the disclosure button accessory, so I needed to implement the checkmark somewhere else. The solution I chose was to create my own checkmark icon and place that in the image portion of the standard cell, over on the left hand side of the cell. I quickly discovered, though, that you can't use the same technique as in the above document. If you change the image of a cell, the changes will not be seen. For some reason, changing the accessory type of a cell is rendered immediately, but not so for the image. The only way I could get the image change to be reflected is to call reloadData: on the table view. After I did that, everything worked. However, I took this opportunity to clean up the clunky code from the example. Instead of finding my old cell, all I do in tableView:didSelectRowAtIndexPath: is set the current active row in our data and call reloadData:. The real work was moved over to the table:cellForRowAtIndexPath: method, which sets the image for each cell according to the active row. First we must handle the row selection. The instance variable activeRow is simply an integer of the currently selected row.
- (void)
tableView:(UITableView *) aTableView didSelectRowAtIndexPath:(NSIndexPath *) indexPath
{
 [aTableView deselectRowAtIndexPath:indexPath animated:NO];

 if(indexPath.row != activeRow) {
    // reset the active account
    activeRow = indexPath.row;

    // tell the table to rebuild itself
    [aTableView reloadData];
 }
}
Next we handle the actual image setting:
- (UITableViewCell *)
tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
 UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:@"Cell"];

 if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"Cell"];

    // set up the cell properties that are the same for all cells on this table
    cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
 }

 // get our text for the row
 cell.text = [self.data forRow:indexPath.row];

 if(indexPath.row == activeRow) {
    cell.image = checkedImage;
 }
 else {
    cell.image = uncheckedImage;
 }

 return cell;
}
After we get a cell to work with, I compare the row with the active row. If it is a match, I use the checked image. If not I use the unchecked image. Here is the final output:

2 comments:

  1. there is a better way since iOS3 - use "highlighted" property of the cell while setting highlightedImage of UIImageView and you can avoid the brutality of realoadData

    ReplyDelete
  2. Thank you SO much for this!
    I've honestly been trying to get this working for over 3 months now, and I can't believe that it was this simple.
    You're site has been bookmarked.

    Thanks a lot!

    ReplyDelete