This bug is caused by having no tableView:estimatedHeightForRowAtIndexPath:
method. It’s an optional part of the UITableViewDelegate
protocol.
This isn’t how it’s supposed to work. Apple’s documentation says:
Providing an estimate the height of rows can improve the user experience when loading the table view. If the table contains variable height rows, it might be expensive to calculate all their heights and so lead to a longer load time. Using estimation allows you to defer some of the cost of geometry calculation from load time to scrolling time.
So this method is supposed to be optional. You’d think if you skipped it, it would fall back on the accurate tableView:heightForRowAtIndexPath:
, right? But if you skip it on iOS 8, you’ll get this behaviour.
What seems to be happening? I have no internal knowledge, but it looks like if you do not implement this method, the UITableView will treat that as an estimated row height of 0
. It will compensate for this somewhat (and, at least in some cases, complain in the log), but you’ll still see an incorrect size. This is quite obviously a bug in UITableView. You see this bug in some of Apple’s apps, including something as basic as Settings.
So how do you fix it? Provide the method! Implement tableView: estimatedHeightForRowAtIndexPath:
. If you don’t have a better (and fast) estimate, just return UITableViewAutomaticDimension
. That will fix this bug completely.
Like this:
- (CGFloat)tableView:(UITableView *)tableView
estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
There are potential side effects. You’re providing a very rough estimate. If you see consequences from this (possibly cells shifting size as you scroll), you can try to return a more accurate estimate. (Remember, though: estimate.)
That said, this method is not supposed to return a perfect size, just a good enough size. Speed is more important than accuracy. And while I spotted a few scrolling glitches in the Simulator there were none in any of my apps on the actual device, either the iPhone or iPad. (I actually tried writing a more accurate estimate. But it’s hard to balance speed and accuracy, and there was simply no observable difference in any of my apps. They all worked exactly as well as just returning UITableViewAutomaticDimension
, which was simpler and was enough to fix the bug.)
So I suggest you do not try to do more unless more is required. Doing more if it is not required is more likely to cause bugs than fix them. You could end up returning 0
in some cases, and depending on when you return it that could lead to the original problem reappearing.
The reason Kai’s answer above appears to work is that it implements tableView:estimatedHeightForRowAtIndexPath:
and thus avoids the assumption of 0
. And it does not return 0
when the view is disappearing. That said, Kai’s answer is overly complicated, slow, and no more accurate than just returning UITableViewAutomaticDimension
. (But, again, thanks Kai. I’d never have figured this out if I hadn’t seen your answer and been inspired to pull it apart and figure out why it works.)]
Note that you may also need to force layout of the cell. You’d think iOS would do this automatically when you return the cell, but it doesn’t always. (I will edit this once I investigate a bit more to figure out when you need to do this.)
If you need to do this, use this code before return cell;
:
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];