9

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

coverback
  • 4,413
  • 1
  • 19
  • 31
  • Are you using stock `UITableViewCell`s or custom cells? – n00bProgrammer Jan 04 '14 at 21:14
  • Can you please add all cellForRowAtIndexPath: method? I think I will be able to help you if you do so :) – Refael.S Jan 04 '14 at 21:40
  • 1
    OK. Here is what I have learnt (painfully) working with `UITableViews`. The `drawRect` method of `UITableView`s is not called 'always'. I suppose you are reusing cells with `dequeueReusableCellWithCellIdentifier`. One thing you could try to do is place a `[yourCell drawRect]` in the `willDisplayCell` method. This method is always called whenever a cell is about to move to visible bounds. Also `setNeedsLayout` and `setNeedsDisplay` are taxing methods on memory, in cells with lots of content. – n00bProgrammer Jan 04 '14 at 21:45
  • @Refael.S I have added the code, although there isn't much in it, I'm pretty sure. Also added how it looks in the storyboard. – coverback Jan 04 '14 at 22:01
  • @n00bProgrammer thanks, but calling `drawRect` is a much worse hack than `layoutIfNeeded`. Also, the problem is for new cells, not for reused ones. – coverback Jan 04 '14 at 22:02
  • I see. Let me get back to you. – n00bProgrammer Jan 04 '14 at 22:04
  • @coverback Did you find any good solution? I am facing the same issue. What solution you used? – Geek Jan 31 '14 at 04:54
  • @Geek No, I am still calling `layoutIfNeeded` as in the question text. – coverback Jan 31 '14 at 10:30
  • @coverback But has it solved your issue? You still see that jerk? – Geek Jan 31 '14 at 10:40
  • @coverback I get that jerk even with `layoutIfNeeded`. Can you show me your full code for `UITableView`? – Geek Jan 31 '14 at 11:05
  • @Geek there isn't anything else to show, code is very simple. Maybe you should create a question of your own. – coverback Jan 31 '14 at 11:47
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/46517/discussion-between-geek-and-coverback) – Geek Jan 31 '14 at 12:59
  • manually forcing the size by hardcoding it in willDisplayCell was the only way I could fix this issue, if. you have a view that has a constant and a dynamic part then you can get the view,width and subtract the constant from it to calculate the variable one manually! – Ramin Mar 25 '20 at 08:47

2 Answers2

4

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.

bilobatum
  • 8,918
  • 6
  • 36
  • 50
  • I think trying to impact layouting is more heavy and complicated than just calling `layoutIfNeeded`. I have thought about two cells, but maintaining two almost exactly same views is more error-prone in the future. – coverback Jan 04 '14 at 22:06
  • It does indeed seem that nothing else is to be done here, so I'll be marking your answer as accepted. I didn't find anything else to fix this, nor any of the other suggestions worked. – coverback Jan 09 '14 at 09:06
0

You've added additional code to the question, so I have another suggestion. In the cell's setData:forMyself: method, try calling setNeedsUpdateConstraints instead of layoutIfNeeded.

bilobatum
  • 8,918
  • 6
  • 36
  • 50
  • Doesn't change anything. In fact, when printing out `needsUpdateConstraints` it is already `YES`, and same for layout, because removing from superview already invalidated it. – coverback Jan 05 '14 at 09:28
  • So, in original code, `[cell setNeedsLayout]` is redundant, I put it there to avoid answers "add `setNeedsLayout`". – coverback Jan 05 '14 at 09:30