Raconteur
Raconteur

Reputation: 1391

UITableView cell getting sent setSelected:NO during layoutView

I am seeing some weird things happening with viewDidLayoutSubviews and UITableViewCells.

I have 2 tables on a page, both of which get sized so they do not scroll (all elements are visible). I take care of the resizing in viewDidLayoutSubviews like this:

- (void)viewDidLayoutSubviews {
    self.orderItemTableViewHeightConstraint.constant = self.orderItemTableView.contentSize.height - [self tableView:self.orderItemTableView heightForHeaderInSection:0];
    self.shippingOptionTableViewHeightConstraint.constant = self.shippingMethodTableView.contentSize.height;

    [self.view layoutIfNeeded];

    self.scrollViewContainerViewHeightConstraint.constant = self.shippingMethodTableView.$bottom;

    [self.view layoutIfNeeded];
}

This works as expected.

However, when shippingOptionTableView is built, it has 3 cells (for this example), each with a model that feeds the label in the cell and a selection marker like this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (tableView == self.shippingMethodTableView) {
        DLShippingOptionTableCell *cell = [tableView dequeueReusableCellWithIdentifier:SHIPPING_OPTION_CELL_IDENTIFIER];

        if (cell == nil) {
            NSString *nibName = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) ? @"DLShippingOptionTableCell~iphone" : @"DLShippingOptionTableCell";

            NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:nibName owner:nil options:nil];

            for (id currentObject in topLevelObjects) {
                if ([currentObject isKindOfClass:[DLShippingOptionTableCell class]]) {
                    cell = (DLShippingOptionTableCell *)currentObject;
                    [cell setupCell];
                    break;
                }
            }
        }

        cell.model = [self.model.shippingOptionArray objectAtIndex:indexPath.row];

        return cell;
    }
    else {
        ...
    }
}

The model gets set in the table cell, which causes the visual state to be updated:

- (void)setModel:(DLShippingOption *)model {
    _model = model;

    NSNumberFormatter *currencyFormatter = [[NSNumberFormatter alloc] init];
    [currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];

    NSString *numberAsString = [currencyFormatter stringFromNumber:[NSNumber numberWithFloat:(model.priceInPennies / 100.0f)]];

    self.shippingLabel.text = [NSString stringWithFormat:@"%@ (%@)", model.optionName, numberAsString];

    [self toggleSelectedState:model.isSelected];
}

- (void)toggleSelectedState:(BOOL)isSelected {
    [self setSelected:isSelected];

    self.radioButtonImageView.image = isSelected ? [UIImage imageNamed: @"radio_button_selected.jpg"] : [UIImage imageNamed: @"radio_button_unselected.jpg"];
}

Here is the problem... the table cell get sent setSelected:NO repeatedly during the layout process. So, even when my model is set to selected, and I update the radio-button graphic (which I would normally do in setModel:) it gets overridden and changed to false.

I had to put this hack in place to make it work (basically adding this bit of code to the cellForRowAtIndexPath in the ViewController, just after setting the cell's model:

if (cell.model.isSelected) {
    [self.shippingMethodTableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}

I this bites... So the question is, why are table cells getting sent setSelected:NO over and over and over during layout, and is there a better way to overcome that?

Upvotes: 0

Views: 248

Answers (1)

Ikkyu
Ikkyu

Reputation: 99

Try using prepareForReuse method and see if it helps. It will be also helpful to see the documentation here:

https://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewCell_Class/Reference/Reference.html

If a UITableViewCell object is reusable—that is, it has a reuse identifier—this method is invoked just before the object is returned from the UITableView method dequeueReusableCellWithIdentifier:. For performance reasons, you should only reset attributes of the cell that are not related to content, for example, alpha, editing, and selection state. The table view's delegate in tableView:cellForRowAtIndexPath: should always reset all content when reusing a cell. If the cell object does not have an associated reuse identifier, this method is not called. If you override this method, you must be sure to invoke the superclass implementation.

Also note that you should only use this method for stuff which is not related to content as pointed out in the documentation. Content related stuff should always be done in tableView:cellForRowAtIndexPath: method

Upvotes: 1

Related Questions