UITableView dynamic cell heights only correct after some scrolling
Solution 1
I don't know this is clearly documented or not, but adding [cell layoutIfNeeded]
before returning cell solves your problem.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TestCell"];
NSUInteger n1 = firstLabelWordCount[indexPath.row];
NSUInteger n2 = secondLabelWordCount[indexPath.row];
[cell setNumberOfWordsForFirstLabel:n1 secondLabel:n2];
[cell layoutIfNeeded]; // <- added
return cell;
}
Solution 2
This worked for me when other similar solutions did not:
override func didMoveToSuperview() {
super.didMoveToSuperview()
layoutIfNeeded()
}
This seems like an actual bug since I am very familiar with AutoLayout and how to use UITableViewAutomaticDimension, however I still occasionally come across this issue. I'm glad I finally found something that works as a workaround.
Solution 3
Adding [cell layoutIfNeeded]
in cellForRowAtIndexPath
does not work for cells that are initially scrolled out-of-view.
Nor does prefacing it with [cell setNeedsLayout]
.
You still have to scroll certain cells out and back into view for them to resize correctly.
This is pretty frustrating since most devs have Dynamic Type, AutoLayout and Self-Sizing Cells working properly — except for this annoying case. This bug impacts all of my "taller" table view controllers.
Solution 4
I had same experience in one of my projects.
Why it happens?
Cell designed in Storyboard with some width for some device. For example 400px. For example your label have same width. When it loads from storyboard it have width 400px.
Here is a problem:
tableView:heightForRowAtIndexPath:
called before cell layout it's subviews.
So it calculated height for label and cell with width 400px. But you run on device with screen, for example, 320px. And this automatically calculated height is incorrect. Just because cell's layoutSubviews
happens only after tableView:heightForRowAtIndexPath:
Even if you set preferredMaxLayoutWidth
for your label manually in layoutSubviews
it not helps.
My solution:
1) Subclass UITableView
and override dequeueReusableCellWithIdentifier:forIndexPath:
. Set cell width equal to table width and force cell's layout.
- (UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [super dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
CGRect cellFrame = cell.frame;
cellFrame.size.width = self.frame.size.width;
cell.frame = cellFrame;
[cell layoutIfNeeded];
return cell;
}
2) Subclass UITableViewCell
.
Set preferredMaxLayoutWidth
manually for your labels in layoutSubviews
. Also you need manually layout contentView
, because it doesn't layout automatically after cell frame change (I don't know why, but it is)
- (void)layoutSubviews {
[super layoutSubviews];
[self.contentView layoutIfNeeded];
self.yourLongTextLabel.preferredMaxLayoutWidth = self.yourLongTextLabel.width;
}
Solution 5
none of the above solutions worked for me, what worked is this recipe of a magic: call them in this order:
tableView.reloadData()
tableView.layoutIfNeeded()
tableView.beginUpdates()
tableView.endUpdates()
my tableView data are populated from a web service, in the call back of the connection I write the above lines.
blackp
Updated on September 07, 2021Comments
-
blackp over 2 years
I have a
UITableView
with a customUITableViewCell
defined in a storyboard using auto layout. The cell has several multilineUILabels
.The
UITableView
appears to properly calculate cell heights, but for the first few cells that height isn't properly divided between the labels. After scrolling a bit, everything works as expected (even the cells that were initially incorrect).- (void)viewDidLoad { [super viewDidLoad] // ... self.tableView.rowHeight = UITableViewAutomaticDimension; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { TableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TestCell"]; // ... // Set label.text for variable length string. return cell; }
Is there anything that I might be missing, that is causing auto layout not to be able to do its job the first few times?
I've created a sample project which demonstrates this behaviour.
-
blackp over 9 yearsI do wonder though, why is it only necessary the first time through? It works just as well if I put the call to
-layoutIfNeeded
in the the cell's-awakeFromNib
. I'd prefer to only calllayoutIfNeeded
where I know why it's necessary. -
BonanzaDriver over 9 yearsActually Ramesh, this shouldn't be needed as long as the tableView.estimatedRowHeight and tableView.rowHeight = UITableViewAutomaticDimension properties are set. Also, make sure the custom cell has appropriate constraints on the various widgets and the contentView.
-
Mustafa over 9 yearsWorked for me! And I would love to know why is it required!
-
Andrei Konstantinov almost 9 yearsDid not work if you render size class, that different from any X any
-
Max almost 9 yearsSame for me. when I first scroll, cell's size is 44px. if I scroll to make the cell out of sight and come back, it is sized properly. Did you find any solution?
-
ImpurestClub over 8 yearsThank you @ashy_32bit this solved it for me as well.
-
mckeejm over 8 yearsthis worked for me, without applying any of the other solutions listed above. Great find.
-
Fattie over 8 yearsit would seem to be a plain bug, @Scenario right? horrible stuff.
-
ypresto over 8 yearsUsing
[cell layoutSubviews]
instead of layoutIfNeeded might be possible fix. Refer stackoverflow.com/a/33515872/1474113 -
z22 over 8 years@AndreyKonstantinov Yup it doesn't work with size class, how do I make it work with size class?
-
GK100 almost 8 yearsWhen I ran into this issue, I also needed to ensure the tableview's frame was correct, so I called [self.tableview layoutIfNeeded] before the tableview was populated (in viewDidLoad).
-
Yuvrajsinh over 7 yearsIt works without size class, any solution with size class?
-
6axter82 about 7 yearsThis helped me (with size class). Just after you request and set the label's text:
cell.labelText.preferredMaxLayoutWidth = cell.labelText.frame.width
-
Robert Wagstaff about 7 yearsBetter than the accepted answer as it is only called when the cell is being loaded from the xib instead of every cell display. Far less lines of code too.
-
Jimmy George Thomas almost 7 yearsworking and definitely better than invoking reloadData twice
-
cicerocamargo over 6 yearsDon't forget to call
super.didMoveToSuperview()
-
lucius degeer over 6 yearsBut this autoshrinks the text... why would one want to have different sizes of text in a table view? Looks terrible.
-
Martin over 6 yearswhich is NOT the recommended way to initialize a cell.
willDisplay
will have better performance thancellForRowAt
. Use the lastest only to instanciate the right cell. -
Martin over 6 yearsBlack magic. Doing it in
viewWillAppear
did not work for me, but doing it inviewDidAppear
did. -
Bptstmlgt over 6 yearsI can't believe I've spent so much time to finally find this working answer. :)
-
PJ_Finnegan over 6 yearsThis is the only WA that worked for me too. Except I don't use isHidden, but set the table's alpha to 0 when adding the data source, then to 1 after reloading.
-
PJ_Finnegan over 6 yearsIt's important to set
estimatedRowHeight
to a value > 0 and not toUITableViewAutomaticDimension
(which is -1), otherwise the auto row height won't work. -
richardpiazza over 6 yearsThis solution 'did' work for me when implemented in the viewDidAppear. It is by no means optimal for the user experience as the table content jumps around as the correct sizing occurs.
-
axunic about 6 yearsi want to give you this world... :)
-
andrew k almost 6 yearsMy similar
reloadData/beginUpdates/endUpdates/reloadData
is also working;reloadData
must be called a second time. Don't need to wrap it inasync
. -
Ivan Smetanin over 5 years@cicerocamargo Apple says about
didMoveToSuperview
: "The default implementation of this method does nothing." -
TheTravloper over 5 yearsthis was far far far far far most better solution that helped me. It didn't corrected the 100% of cell but upto 80% were taking their actual size. Thanks a lot
-
TNguyen about 5 years@IvanSmetanin it doesn't matter if the default implementation doesn't do anything, you should still call the super method as it could actually do something in the future.
-
Fattie over 4 years(Just btw you should DEFINITELY call super. on that one. I have never seen a project where the team overall doesn't subclass more than once cells, so, of course you have to be sure to pick them all up. AND as a separate issue just what TNguyen said.)
-
Julian Wagner over 4 yearsIs that supposed to be in TableView or in the Cell?
-
Shakeel Ahmed over 4 yearsamazing i have tried alot of examples but fails you are demon
-
tamtoum1987 over 4 yearsi tried all others solution, ony your solution worked for thank you for sharing it
-
A.J. Hernandez about 4 yearsSame as @martin
-
Martin about 4 years2 years after, I'm reading that old comment I wrote down. This was a bad advice. Even if
willDisplay
have better performance, it iS recommended to initialise your cell UI incellForRowAt
when the layout is automatic. Indeed, the layout is computed by UIKit after cellForRowAt et before willDisplay. So if your cell height depends of its content, initialize the label content (or whatever) incellForRowAt
. -
Govani Dhruv Vijaykumar almost 4 yearsFor swift 5 this worked even in collection view, god bless you, bro.
-
Darrow Hartman almost 4 yearsFor me, this returned the correct height and layout of the cell, but it had no data in it
-
JAHelia almost 4 yearsTry setNeedsDisplay() after those lines
-
KoreanXcodeWorker almost 4 yearsThis ONLY helped me working great. The above all did nothing to me(using tableview cell in another tableview cell, Swift 4.0).
-
Cam Connor almost 4 yearsI never stopped to think it had to do with an incorrect width.
cell.frame.size.width = tableview.frame.width
thencell.layoutIfNeeded()
in thecellForRowAt
function did the trick for me -
Nazar Medeiros almost 4 yearsI also have an async block but it is needed? Is there no alternative?
-
KSR over 3 yearsThis helped me a lot. Thank you rintaro.
-
Zaheer Moola over 2 yearsI am so glad I scrolled far down enough. This was the only solution for me. All I needed was the first part. Not a fan of reloading tableView twice but it certainly works
-
Bucket over 2 years@JAHelia Thank you brother, it works !!!