Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

(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.

like image 652
Jordan Johns Avatar asked May 07 '13 17:05

Jordan Johns


1 Answers

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 :)

like image 79
Jordan Johns Avatar answered Oct 27 '22 10:10

Jordan Johns