iOS: CellForRowAtIndexPath cells are getting mixed up

Going to start off by saying I’ve seen these questions:

iOS: UITableView mixes up data when scrolling too fast

  • How to know which Unicode Emoji flags are supported per iOS or Android version?
  • How to make a real private instance variable?
  • Is it possible to change the title of PKAddPassesViewController ?
  • What is an efficient way to Merge two iOS Core Data Persistent Stores?
  • AlamoFire Download in Background Session
  • Phonegap: Error when installing BarcodeScanner for iOS
  • (custom) UITableViewCell's mixing up after scrolling

    Items mixed up after scrolling in UITableView

    The first and last seemed very relevant to my problem, however I am fairly certain that I have logic for each section to determine what should appear in the cell (the data), and yet they still get mixed up.

    The following is the relevant code:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    //Note: the if (cell == nil) thing is no longer required in iOS 6
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }
    
    
    if (closestRenter != nil)
    {
        NSLog(@"CLOSEST RENTER!");
        [self setupCellsWithClosestRenterCell:cell atIndexPath:indexPath];
    }
    else
    {
        NSLog(@"NO CLOSEST RENTER");
        [self setupCellsWithNoClosestRenterCell:cell atIndexPath:indexPath];
    }
    
    if (indexPath.section == 0)
    {
        for (UIView *view in cell.contentView.subviews)
        {
            NSLog(@"WHAT THE HECK");
            [view removeFromSuperview];
        }
    }
    
    return cell;
    

    }

    Relevant info here:

    1) ClosestRenter is NOT nil… it exists. So the else clause should never be executed… and that is the case.

    2) Within the code:

    [self setupCellsWithClosestRenterCell:cell atIndexPath:indexPath];
    

    There is a simple:

    if (indexPath.section == 0)
    {
        cell.textLabel.text = @"PLACE HOLDER";
    }
    else
    {
        // Populate the cell with data. (creates a view (with controller etc) and loads it into the cell)
    }
    

    3) There are 2 sections at all times.

    The problem is that section 0 (the first section) should have nothing more than that placeholder string. Section 1 should contain my custom subviews (in cells, which it does).

    Section 0 initially has just the placeholder string, however once I scroll down (and the section is no longer visible) and scroll back up (quickly) it sometimes has a seemingly random cell from section 1 in there… what the heck? How? I’m reluctant to blame cell reuse but at this point outside of something really silly I don’t know what it is.

    The disturbing part here is that the cell in section 0 (there is only 1 row there) has no subviews. But when I scroll up and down fast it gets one (from section 1 apparently) and then I get the “WHAT THE HECK” log messages…

    It should be worth mentioning that with the for loop (the one with the what the heck messages) does solve the problem (as it removes the unwanted subviews) but there has to be a better way. It feels wrong right now.

    Any ideas?

    (Feel free to mark this as a duplicate, but I’m fairly certain there is something else going on here).

    Thanks.

    3 Solutions Collect From Internet About “iOS: CellForRowAtIndexPath cells are getting mixed up”

    So after a little bit of frustration, and careful analysis I found out why the cells were getting mixed up.

    My assumption about cell reuse (particularly with the identifiers) was the problem.

    Previously I was doing this:

    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }
    

    Which is great and all, however there is a critical problem. All cells were technically the same underneath… after they are all allocated (not nil) the system could not determine which cell to reuse no matter what section it was.

    This meant that any cell could be grabbed from the queue, with whatever it had in it, and stuck anywhere (despite my checks to make sure section 1 stuff went in section 1, and section 0 stuff (the fake renter) stayed in there).

    The solution:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    //Note: the if (cell == nil) thing is no longer required in iOS 6
    static NSString *CellIdentifier1 = @"Cell";
    static NSString *CellIdentifier2 = @"Cell2";
    
    UITableViewCell *cell;
    
    if (indexPath.section == 0)
    {
        if (cell == nil)
        {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier1];
        }
        else
        {
            cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier1];
        }
    
    }
    else
    {
        if (cell == nil)
        {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier1];
        }
        else
        {
            cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier2];
        }
    }
    
    
    if (closestRenter != nil)
    {
        NSLog(@"CLOSEST RENTER!");
        [self setupCellsWithClosestRenterCell:cell atIndexPath:indexPath];
    }
    else
    {
        NSLog(@"NO CLOSEST RENTER");
        [self setupCellsWithNoClosestRenterCell:cell atIndexPath:indexPath];
    }
    
    return cell;
    

    }

    As you can see, section 0 will get its own cell identifier. As will section 1. The result is that when a cell is to be dequeued, it will check which section the indexPath is currently at and grab the correct cell.

    Ugh, such a frustrating problem but now it all makes sense 🙂

    Adding an else solved my problem.
    Where I reseted any changes that were made to the cell.

    if (! self.cell) {
    
    self.cell = [[LanguageCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
    
    self.cell.accessoryType = UITableViewCellAccessoryNone;
    }
    else {
    self.cell.checkImage.image = NO;
    
    }
    

    There are a couple of ways to deal with this cell reuse problem (and that is what the problem is), and the way you’re doing it now is ok. The problem is that when you scroll down and back up again, the cell that’s returned for section 0 could be a cell that was previously used for section 1, so it will have any subviews you put in there.

    Another way to handle this, is to create two different prototype cells in the storyboard, and return the one with a simple label for section 0, and return the other one, with any subviews added in IB, for section 1. This way, you will always get the correct type of cell for each section without having to remove any subviews — you only need to re-populate the cell with the correct data.