views:

2275

answers:

5

I'm using a UITableView to allow selection of one (of many) items. Similar to the UI when selecting a ringtone, I want the selected item to be checked, and the others not. I would like to have the cell selected when touched, then animated back to the normal color (again, like the ringtone selection UI).

A UIViewController subclass is my table's delegate and datasource (not UITableViewController, because I also have a toolbar in there).

I'm setting the accessoryType of the cells in cellForRowAtIndexPath:, and updating my model when cells are selected in didSelectRowAtIndexPath:. The only way I can think of to set the selected cell to checkmark (and clear the previous one) is to call [tableView reloadData] in didSelectRowAtIndexPath:. However, when I do this, the animating of the cell deselection is weird (a white box appears where the cell's label should be). If I don't call reloadData, of course, the accessoryType won't change, so the checkmarks won't appear.

I suppose I could turn the animation off, but that seems lame. I also toyed with getting and altering the cells in didSelectRowAtIndexPath:, but that's a big pain.

Any ideas? Abbreviated code follows...

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell* aCell = [tableView dequeueReusableCellWithIdentifier:kImageCell];
    if( aCell == nil ) {
     aCell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:kImageCell];
    }

    aCell.text = [imageNames objectAtIndex:[indexPath row]];
    if( [indexPath row] == selectedImage ) {
     aCell.accessoryType = UITableViewCellAccessoryCheckmark;
    } else {
     aCell.accessoryType = UITableViewCellAccessoryNone;
    }
    return aCell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    selectedImage = [indexPath row]
    [tableView reloadData];
}
+3  A: 

I had this exact same problem and never solved it.

In the end it was moot for me because I had to listen for changes in the model (the selection could change outwith the user's control if a connection was lost) so I just changed the model and let the notification reload the data, but it didn't look too pretty (it was like a non-animated deselection, but one run loop iteration late!)

I suspect the only way to do it is, as you suggest, keeping a cache of references to the cells themselves. This shouldn't be too messy if you write a UITableViewCell subclass to encapsulate the logic, overriding -prepareForReuse.

Still, not pretty, and if the ringtone guys at Apple had to do it this way, they ought to have pressed the UITableView team harder to fix it!

EDIT:

Well, what do you know?! This is really easy after all. -[UITableView cellForRowAtIndexPath] will give you the actual cells (or nil if they're offscreen) and you can just change the accessoryType.

hatfinch
+4  A: 

I solved it like hatfinch suggested in his edit. My code sample make sure there's only one row checked but I'm sure you can tweak it if thats not what you need.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

int newRow = [indexPath row];
int oldRow = [lastIndexPath row];

if (newRow != oldRow)
{
 UITableViewCell *newCell = [tableView cellForRowAtIndexPath:
        indexPath];
 newCell.accessoryType = UITableViewCellAccessoryCheckmark;

 UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:
        lastIndexPath];
 oldCell.accessoryType = UITableViewCellAccessoryNone;

 lastIndexPath = indexPath;
}

[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Ron Srebro
A: 

Ron Srebro , Your answer was the best I have seen so far!

Aneesh Abraham
A: 

I have a cleaner way of doing it.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];

    if (cell.accessoryType == UITableViewCellAccessoryNone) {
        cell.accessoryType = UITableViewCellAccessoryCheckmark;

    } else {
        cell.accessoryType = UITableViewCellAccessoryNone;
    }
}
JeremySpouken
A: 

This will work as desired...

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {


int newRow = [indexPath row];
int oldRow = [lastIndexPath row];


if (newRow == oldRow){
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];

    if (cell.accessoryType == UITableViewCellAccessoryNone) {
        cell.accessoryType = UITableViewCellAccessoryCheckmark;

    } else {
        cell.accessoryType = UITableViewCellAccessoryNone;
    }

     lastIndexPath = indexPath;
}


if (newRow != oldRow)
{
    UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
    newCell.accessoryType = UITableViewCellAccessoryCheckmark;

    UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:lastIndexPath];
    oldCell.accessoryType = UITableViewCellAccessoryNone;

    lastIndexPath = indexPath;
}


[tableView deselectRowAtIndexPath:indexPath animated:YES];

}
Bill DeWeese