Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

setNeedsLayout relayouts the cell only after is has been displayed

I have a table view with cells, which sometimes have an optional UI element, and sometimes it has to be removed. Depending on the element, label is resized.

When cell is initialised, it is narrower than it will be later on. When I set data into the label, this code is called from cellForRowAtIndexPath:

if (someFlag) {
    // This causes layout to be invalidated
    [flagIcon removeFromSuperview];
    [cell setNeedsLayout];
}

After that, cell is returned to the table view, and it is displayed. However, the text label at that point has adjusted its width, but not height. Height gets adjusted after a second or so, and the jerk is clearly visible when all cells are already displayed.

Important note, this is only during initial creation of the first few cells. Once they are reused, all is fine, as optional view is removed and label is already sized correctly form previous usages.

Why isn't cell re-layouted fully after setNeedsLayout but before it has been displayed? Shouldn't UIKit check invalid layouts before display?

If I do

if (someFlag) {
    [flagIcon removeFromSuperview];
    [cell layoutIfNeeded];
}

all gets adjusted at once, but it seems like an incorrect way to write code, I feel I am missing something else.


Some more code on how cell is created:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    ProfileCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
    [cell setData:model.items[indexPath.row] forMyself:YES];
    return cell;
}

// And in ProfileCell:
- (void)setData:(Entity *)data forMyself:(BOOL)forMe
{
    self.entity = data;

    [self.problematicLabel setText:data.attributedBody];
    // Set data in other subviews as well

    if (forMe) {
        // This causes layouts to be invalidated, and problematicLabel should resize
        [self.reportButton removeFromSuperview];
        [self layoutIfNeeded];
    }
}

Also, if it matters, in storyboard cell looks like this, with optional constraint taking over once flag icon is removed: Constraints in the storyboard

like image 531
coverback Avatar asked Jan 04 '14 18:01

coverback


1 Answers

I agree that calling layoutIfNeeded seems wrong, even though it works in your case. But I doubt that you're missing something. Although I haven't done any research on the manner, in my experience using Auto Layout in table cells that undergo a dynamic layout is a bit buggy. That is, I see herky jerky layouts when removing or adding subviews to cells at runtime.

If you're looking for an alternative strategy (using Auto Layout), you could subclass UITableViewCell and override layoutSubviews. The custom table cell could expose a flag in its public API that could be set in the implementation of tableView:cellForRowAtIndexPath:. The cell's layoutSubviews method would use the flag to determine whether or not it should include the optional UI element. I make no guarantees that this will eliminate the problem however.

A second strategy is to design two separate cell types and swap between the two in tableView:cellForRowAtIndexPath: as necessary.

like image 112
bilobatum Avatar answered Nov 15 '22 07:11

bilobatum