1

Till today I thought I understood UITableView properly but that perception has changed as I have been fighting scrolling and content mismatch while scrolling issues.

I have a small table view with potentially 20-30 rows depending on user content. When I scroll (a bit faster than usual), the content gets mixed up. Some accessory views get interchanged, background colors get swapped or overwritten; sometimes even the text changes.

I use reuse identifiers for some of cells but I am not sure its even working out properly.

Issues:

  1. Clearly a single reuse identifier is not cutting it in case — as the first and the last cell swap values.

  2. With 3 reuse identifiers for first, last and remaining cells (contain settings), the accessory views get interchanged between some cells that contain settings, i.e. in some cases I use switch controls, in others I use an accessory detail button and there is blasphemy.

  3. Even with 5 reuse identifiers for different types of content, the accessory views get mismatched with background colors of other cells.

I have been writing table views for a while and I have never encountered these issues before. So I have one simple question — When do you create reuse identifiers?

  • only when the text/image content is different?
  • when accessory views are same? i.e. for different accessory views create different reuse identifiers

I am completely lost on this so if you can really churn out an explanation, I would be really grateful.

EDIT: I have been working on this for the last 5 hours and have gone through a lot of literature and open/closed issues on StackOverflow. I understand what reusable cells are. The provided thread doesn't answer the very specific questions.

Here is some code:

// previously had 3 different types of cells; currently doing with one only
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *cellIdentifier = [self cellIdentifierForIndexPath:indexPath];

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (!cell)
    {
        cell = [[ESBasicTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
    }

    [self configureCell:cell atIndexPath:indexPath];
    return cell;
}

- (NSString *)cellIdentifierForIndexPath:(NSIndexPath *)indexPath
{
    static NSString *FirstCellId = @"First";
    static NSString *SecondCellId = @"Second";
    static NSString *ThirdCellId = @"Third";
    static NSString *ForthCellId = @"Forth";
    static NSString *FifthCellId = @"Fifth";
    static NSString *SixthCellId = @"Sixth";

    NSString *cellIdentifier = nil;
    if (indexPath.section == kSectionFirst && indexPath.row == kRowFirst)
    {
        cellIdentifier = FirstCellId;
    }
    else if (indexPath.section == kSectionFirst && indexPath.row == kRowSecond)
    {
        cellIdentifier = SecondCellId;
    }
    else if (indexPath.section == kSectionSecond)
    {
        cellIdentifier = ThirdCellId;
    }
    else if (indexPath.section == kSectionThird)
    {
        cellIdentifier = ForthCellId;
    }
    else if (indexPath.section == kSectionSettings)
    {
        cellIdentifier = FifthCellId;
    }
    else if (indexPath.section == kSEctionFifth)
    {
        cellIdentifier = SixthCellId;
    }

    return cellIdentifier;
}

The configureCell:atIndexPath method results in other configuration calls; one of them being configuration for settings:

- (void)configureCellForSettings:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
    cell.selectionStyle = UITableViewCellSelectionStyleNone;

    switch (indexPath.row)
    {
        case kRowNotificationSetting:
        {
            cell.textLabel.text = @"Notifications";
            cell.accessoryType = UITableViewCellAccessoryDetailButton;
            cell.imageView.image = [[UIImage imageNamed:@"cell_notification_icon"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
            break;
        }
        case kRowSomeOtherSetting:
        {
            cell.textLabel.text = @"Auto";
            cell.imageView.image = [[UIImage imageNamed:@"cell_auto_icon"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];

            UISwitch *switchView = [[UISwitch alloc] initWithFrame:CGRectZero];
            [switchView addTarget:self action:@selector(changed:) forControlEvents:UIControlEventValueChanged];
            [switchView setOn:...];

            cell.accessoryView = switchView;
            break;
        }
        default:
            break;
    }
}

Some meaningful text has been stripped out (unfortunately). The forth section is for the settings but this is just an example.

Edit: Here is configureCell:atIndexPath:

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.section == kSectionFirst)
    {
        // ...
        [self configureFirstCell:cell atIndexPath:indexPath];
    }
    else if (indexPath.section == kSectionSecond)
    {
        // ...
        [self configureSecondCell:cell atIndexPath:indexPath];
    }
    else if (indexPath.section == kSectionThird)
    {
        // add ...
        [self configureThirdCell:cell atIndexPath:indexPath];
    }
    else if (indexPath.section == kSectionSettings)
    {
        // settings
        [self configureCellForSettings:cell atIndexPath:indexPath];
    }
    else if (indexPath.section == kSectionFifth)
    {
        // delete button
        [self configureFifthCell:cell atIndexPath:indexPath];
    }
}

Please note — I had to rename a lot of the code to have it on StackOverflow.

p0lAris
  • 4,750
  • 8
  • 45
  • 80
  • possible duplicate of [UITableView dequeueReusableCellWithIdentifier Theory](http://stackoverflow.com/questions/3552343/uitableview-dequeuereusablecellwithidentifier-theory) – Adam Jenkins May 03 '15 at 22:22
  • Questions are very different. Please see the update. Thanks. – p0lAris May 03 '15 at 22:25
  • Update your question with your `cellForRowAtIndexPath` method so people can point out issues. – rmaddy May 03 '15 at 22:25
  • I don't mean to by trite, but it sounds like you don't actually fully understand what reusable cells are. – Adam Jenkins May 03 '15 at 22:28
  • Clearly, I believe the same. I may not understand it 'completely' but I understand the stuff that's mentioned in the answer. – p0lAris May 03 '15 at 22:34
  • Your `configureCell:atIndexPath:` method and your `cellIdentifierForIndexPath:` don't match at all. That's a big problem. And you posted the `configureCellForSettings:atIndexPath:` method, not the `configureCell:atIndexPath:`method. – rmaddy May 03 '15 at 22:36
  • I thought it wouldn't be necessary. I have updated with code for `configureCell:atIndexPath:`. It will be some work for me to post internal code for other cell configurations. :/ – p0lAris May 03 '15 at 22:40
  • One big problem I see with the `configureCellForSettings:atIndexPath:` method is that you don't set/reset the exact same set of properties for all possible conditions. If you set the `accessoryView` (for example) in one situation, you need to set it (or reset it) for ALL situations. This is due to cell reuse. – rmaddy May 03 '15 at 22:43
  • I see. This was my secondary question to the answer below by Adam. I think I see what the problem is. And this basically corresponds to all properties like background color, text alignment, etc? – p0lAris May 03 '15 at 22:44
  • Correct - a given set of properties for a given cell reuse identfier must all be set or reset for all conditions. – rmaddy May 03 '15 at 22:49

1 Answers1

3

I only marked this as a duplicate because it sounds like you are struggling with the concept of what is actually happening when you call dequeueReusableCellWithIdentifier:

Whenever you call dequeueReusableCellWithIdentifier: to get a cell, try to conceptualize that you are being returned one of the cells that you have previously generated and added content to that has the same identifier (which is, in fact, exactly what you are doing). The cell that is being returned thusly, may already have accessory views/images set, etc, and you may need to unset/remove these depending on your use case.

If you have several types of cells (where the layout/content differs dramatically) then you may need to consider using multiple reuse identifiers - a reuse identifier for each type of cell, so you can more easily reuse cells, rather than resetting all accessory views/images/etc every time you reuse a cell.

Adam Jenkins
  • 51,445
  • 11
  • 72
  • 100
  • 'The cell that is being returned thusly, may already have accessory views/images set, etc, and you may need to unset/remove these depending on your use case.' So are you trying to say that I actually have to manually say — `cell.accessoryView = nil; cell.accessoryType = ...` to get it to work (again, this is just a sample question) and this might end up solving everything. – p0lAris May 03 '15 at 22:37
  • The second paragraph nailed it for me. Thank you very much. – p0lAris May 03 '15 at 22:51