Gup3rSuR4c
Gup3rSuR4c

Reputation: 9490

Problem with how UITableViewCell is created by UITableView on reloadData

I know there's similar questions to this, but the approved answers don't seem to be working for me. So, my scenario is that I have a UITableView and I want to add and remove items by scanning a bar code. All that works fine except I can't get the UITableView to display the updated information. The problem specifically comes from the tableView:cellForRowAtIndexPath: method on every other reload after the initial one. More specifically the cell is always not nil, so it skips over the new cell creation logic.

For other questions like mine, the answer is that the cell identifier is the problem. Well, I tried messing around with that and it didn't work. Here's my code:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = nil;

    if (indexPath.section < vehicle.inventoryCategoriesCount) {
        cell = [tableView dequeueReusableCellWithIdentifier:@"ModelCell"];

        if (cell == nil) {
            cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"ModelCell"] autorelease];

            NSString *model = [[[vehicle.inventory filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"category == %@", [vehicle.inventoryCategories objectAtIndex:indexPath.section]]] valueForKeyPath:@"@distinctUnionOfObjects.model"] objectAtIndex:indexPath.row];

            cell.textLabel.text = model;
            cell.detailTextLabel.text = [NSString stringWithFormat:@"%d", [[vehicle.inventory filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"model == %@", model]] count]];
            cell.selectionStyle = UITableViewCellSelectionStyleNone;
        }
    } else {
        cell = [tableView dequeueReusableCellWithIdentifier:@"RemoveCell"];

        if (cell == nil) {
            cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"RemoveCell"] autorelease];

            cell.textLabel.text = @"Remove an Item";
            cell.textLabel.textColor = [UIColor redColor];
            cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
        }
    }

    return cell;
}

So, in this example code, I have two different cell identifiers for the separate sections. They are ModelCell and RemoveCell. Well, they don't work as a solution because nothing happens. If I change the cell identifier when I'm allocating a new cell it works because it's simply wiping everything since the identifiers don't match, but I'm going to assume that that is wrong and that there should be a better solution to this issue or I'm simply not doing something right.

I'd appreciate some help on this. I've spent a day on this chunk of code so far and I have not gotten anywhere and I'd like to get it fixed and move on to other part of my app...

Thanks in advance for any help!

UPDATE

Thanks to @fluchtpunkt the problem has been resolved. For anyone else who may run into this in the future, here's the corrected code. I decided to make the identifier even more unique by appending the section number to it.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = nil;

    if (indexPath.section < vehicle.inventoryCategoriesCount) {
        NSString *identifier = [NSString stringWithFormat:@"ModelCell-%d", indexPath.section];

        cell = [tableView dequeueReusableCellWithIdentifier:identifier];

        if (cell == nil) {
            cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identifier] autorelease];

            cell.selectionStyle = UITableViewCellSelectionStyleNone;
        }

        NSString *model = [[[vehicle.inventory filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"category == %@", [vehicle.inventoryCategories objectAtIndex:indexPath.section]]] valueForKeyPath:@"@distinctUnionOfObjects.model"] objectAtIndex:indexPath.row];

        cell.textLabel.text = model;
        cell.detailTextLabel.text = [NSString stringWithFormat:@"%d", [[vehicle.inventory filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"model == %@", model]] count]];
    } else {
        cell = [tableView dequeueReusableCellWithIdentifier:@"RemoveCell"];

        if (cell == nil) {
            cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"RemoveCell"] autorelease];

            cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
            cell.textLabel.textColor = [UIColor redColor];
        }

        cell.textLabel.text = @"Remove an Item";
    }

    return cell;
}

UPDATE

The final code version which corrects my misunderstanding of how the identifier works. I figured I'd keep it simple, so I named the identifiers after the cell style type.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = nil;

    if (indexPath.section < vehicle.inventoryCategoriesCount) {
        cell = [tableView dequeueReusableCellWithIdentifier:@"Value1"];

        if (cell == nil) {
            cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"Value1"] autorelease];

            cell.selectionStyle = UITableViewCellSelectionStyleNone;
        }

        NSString *model = [[[vehicle.inventory filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"category == %@", [vehicle.inventoryCategories objectAtIndex:indexPath.section]]] valueForKeyPath:@"@distinctUnionOfObjects.model"] objectAtIndex:indexPath.row];

        cell.textLabel.text = model;
        cell.detailTextLabel.text = [NSString stringWithFormat:@"%d", [[vehicle.inventory filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"model == %@", model]] count]];
    } else {
        cell = [tableView dequeueReusableCellWithIdentifier:@"Default"];

        if (cell == nil) {
            cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Default"] autorelease];

            cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
            cell.textLabel.textColor = [UIColor redColor];
        }

        cell.textLabel.text = @"Remove an Item";
    }

    return cell;
}

Upvotes: 0

Views: 1896

Answers (3)

Lineesh K Mohan
Lineesh K Mohan

Reputation: 1712

You definitely reuse your cells . if you put your configuration inside if(cell == nil) , it works when no cell is reused . so please put the following code snippet outside of if(cell == nil) condition------------

if(cell == nil) {`` // enter code here } NSString *model = [[[vehicle.inventory filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"category == %@", [vehicle.inventoryCategories objectAtIndex:indexPath.section]]] valueForKeyPath:@"@distinctUnionOfObjects.model"] objectAtIndex:indexPath.row];

        cell.textLabel.text = model;
        cell.detailTextLabel.text = [NSString stringWithFormat:@"%d", [[vehicle.inventory filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"model == %@", model]] count]];
        cell.selectionStyle = UITableViewCellSelectionStyleNone;

Upvotes: 0

ferdil
ferdil

Reputation: 1300

Alex - if I look at your code right at the top, you're doing this:

cell = [tableView dequeueReusableCellWithIdentifier:@"ModelCell"];

However, all the examples (and my code) require a static NSString for the CellIdentifier

static NSString *MyIdentifier = @"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];

This then works the intended way.

Upvotes: 0

Matthias Bauch
Matthias Bauch

Reputation: 90117

put your cell configuration outside of if (cell == nil) { ... } The if condition is only true if no cell could be reused. And you definitely want to reuse your cells. So configure them when you have a valid cell.

Like this:

if (indexPath.section < vehicle.inventoryCategoriesCount) {
    cell = [tableView dequeueReusableCellWithIdentifier:@"ModelCell"];

    if (cell == nil) {
        // only create a new cell if a dequeue was not successful.
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"ModelCell"] autorelease];
    }
    // whatever happened before you have a valid cell here.

    // configure cell:
    NSString *model = [[[vehicle.inventory filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"category == %@", [vehicle.inventoryCategories objectAtIndex:indexPath.section]]] valueForKeyPath:@"@distinctUnionOfObjects.model"] objectAtIndex:indexPath.row];

    cell.textLabel.text = model;
    cell.detailTextLabel.text = [NSString stringWithFormat:@"%d", [[vehicle.inventory filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"model == %@", model]] count]];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
} 

if you want to optimize your code you could move the options that are the same for every cell inside the (cell == nil) condition. For example setting the selectionStyle, or changing the textcolor

Upvotes: 3

Related Questions