TableView Cell reuse and unwanted checkmarks - this is killing me
Asked Answered
C

1

11


Apple's iOS TableView and cell reuse is killing me. I searched and searched and studied, but can't find good docs or good answers. The problem is that when the TableView reuses cells things like Checkmarks (cell accessory) set on a selected Cell are repeated in the cells further down in the table view. I understand that cell reuse is by design, due to memory constraints, but if you have a list with say 50 items, and it starts setting extra checkmarks where they're not wanted, this makes whole endeavor useless.

All I want to do is set a checkmark on a cell I've selected. I've tried this using my own custom cell class, and standard cells generated by a boiler plate TableView class, but it always ends up the same.

Apple even have an example project called TouchCell you can download from the dev center, that is supposed to show a different way of setting a checkmark using a custom cell with an image control on the left. The project uses a dictionary object for a data source instead of a muteable array, so for each item there is a string value and bool checked value. This bool checked value is supposed to set the checkmark so it can track selected items. This sample project also displays this goofy behavior as soon as you populate the TableView with 15+ cells. The reuse of cells starts setting unwanted check marks.

I've even tried experimenting with using a truely unique Cell Identifier for each cell. So instead of each cell having something like @"Acell" I used a static int, cast to a string so the cells got @"cell1", @"cell2" etc. During testing though, I could see that hundreds of new cells where generated during scrolling, even if the table only had 30 items.

It did fix the checkmark repeat problem, but I suspect the memory usage was going way too high.

It's as though the cells that are not currently in the viewable area of the table are created all over again when they are scrolled back into view.

Has anyone come up with an elegant solution to this irritating behavior?

Claudie answered 11/6, 2011 at 10:1 Comment(0)
A
17

cell reusing can be tricky but you have to keep 2 things in mind:

  • Use one identifier for one type of cell - Using multiple identifiers is really only needed when you use different UITableViewCell-subclasses in one table view and you have to rely on their different behaviour for different cells
  • The cell you reuse can be in any state, which means you have to configure every aspect of the cell again - especially checkmars / images / text / accessoryViews / accessoryTypes and more

What you need to do is to create a storage for your checkmark states - a simple array containing bools (or NSArray containing boolean NSNumber objects respectively) should do it. Then when you have to create/reuse a cell use following logic:

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *reuseIdentifier = @"MyCellType";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
    if(cell == nil) {
        /* create cell here */
    }
    // Configure cell now
    cell.textLabel.text = @"Cell text"; // load from datasource
    if([[stateArray objectAtIndex:indexPath.row] boolValue]) {
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
    } else {
        cell.accessoryType = UITableViewCellAccessoryNone;
    }
    return cell;
}

then you will have to react on taps:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [stateArray replaceObjectAtIndex:indexPath.row withObject:[NSNumber numberWithBool:![[stateArray objectAtIndex:indexPath.row] boolValue]]];
    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}

Just remember to use NSMutableArray for your data store ;)

Able answered 11/6, 2011 at 14:32 Comment(11)
Thanks for the suggestion Martin. I will give it a try, though it does look like a variation of the code in the TouchCell project from the DevCenter (apple code). They use a Dictionary class to store the BOOL value and the cell text. It works fine with just a few cells (9 or 10) but screws up in the same way when cells are being reused for 20 to 30 or more ... BTW, I tried running a test app through instruments earlier today, the one where I don't reuse cells at all and it actually used less memory than the preferred method. (If i understand instruments correctly!).Claudie
i didn't have a look at their code acutually but it is ver likely they look similar because this is the way you want to go. maybe you should upload your code somewhere and post the link as a comment hereAble
I'm just making a single view app to try out your suggestion. I'll create two muteable arrays, one for display data (NSString values) and a second array for BOOL states as you suggested.Claudie
are you using multiple sections?.. i don't think we can do more for you without the code :(Able
I'll will post some results, Martin. I'm just trying to write some code cook some food at the same time. You're not in Denmark are you?Claudie
Martin, I think your suggestion works, and even better, I think I understand why it works! Am I correct in thinking that, when a cell or group of cells go out of view they are destroyed or released? And they will never "remember" things like accessories?Claudie
BTW, the project I'm working on (as a learning experience will use Grouping and will need an Array of Arrays. This will get messy if I have to create a cell state array for every array in the groups. Do you think a dictionary object would be better for this?Claudie
cells aren't destroyed, the are reused. so when it goes off the screen it is put in a pool. when you have to return a cell for the next row that comes into the screen you grab any cell out of the pool and configure it for your how you need it. when you get the cell (dequeReusable..) it will be in exactly the same state it left the screen (= same state you configured it in the last cellForRowAtIndexPath). it doesn't really matter what you use as datasource. NSIndexPath is very handy for navigating through arrays of arrays but you can use dicts too.Able
Martin, thanks for your help and code. It works great, but for grouped tables with many sections, a state array for each data source might not scale well. I'm going to try to dive into Core Data. Anyway, I've saved your examples because they solve the problem perfectly! Cheers, NickClaudie
Hey Martin, if you could please, take a look at this issue that I'm currently having. It's extremely similar to this problem and I have not been able to solve it, nor has anyone else #21127154Orangutan
@Martin, thank you so much!...you also helped me a lot with cell reuse!Dolor

© 2022 - 2024 — McMap. All rights reserved.