Question

I'm constructing a UITableView with variable height custom table cells, their height determined by the size of a contained multi-line UILabel. I've got the tableView:heightForRowAtIndexPath: delegate method wired up and calculating the final height correctly using sizeWithFont:constrainedToSize:.

I've run across strange issue: by the time the data source method tableView:cellForRowAtIndexPath: is called, the correct per-row height has already been determined as described above, but the frame of the cell does not match that height. Instead, the frame.size.height property of the cell is the default cell height of the table view (86 px, as I've set it in Interface Builder, the correct height when the contained UILabel has just one line of text), instead of being the height that tableView:heightForRowAtIndexPath: determined correct for that index path.

I'm producing the cells in cellForRowAtIndexPath: using dequeuing, that is,

// Using storyboards, this never returns nil, no need to check for it
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:@"SomeIdentifier"];
NSLog(@"%f", cell.frame.size.height); // 86, not correct if the cell contains a multi-line UILabel

It seems, then, that whatever iOS is doing behind the scenes, the dequeuing is not setting the frame property of the cell to match the calculated height. This in itself is not that surprising, dequeuing concerns itself with cell instances, not their geometry. The cells are rendered correctly, though, so the height property is being set somewhere, but it happens after cellForRowAtIndexPath:.

So: when I initially populate the table view, cell.frame.size.height is 86 for all the cells as they appear for the first time when I scroll the list downwards. Since the correct geometry is set sometime after the first cellForRowAtIndexPath: for each row before it's displayed, when I scroll back up, the height property is correct for each cell that comes back into view after being reused.

After this I can scroll the table view back and forth at will, and the height property remains correct for each cell from that point on.

What's the correct way of getting the correct cell height the first time around, before any dequeue-based reuse happens? I need this to do a bit of re-positioning of the subviews of the table cell. Do I need to manually call heightForRowAtIndexPath: in cellForRowAtIndexPath: and then manually set the frame of the freshly created CustomCell instance to match that height? This seems redundant, and I'd need to create a mechanism to detect when the cell is created for the first time with the wrong frame height vs. when it is dequeued with the correct frame height later to avoid this redundancy.

So, if someone can shed some light into what the logic is behind this, I'd appreciate it.

Was it helpful?

Solution

As suggested by Flexo, answering this myself is apparently better than adding an edit to the question. So, here's the previous edit as an answer:


Nevermind, I should read the docs better. I can get the correct frame in the tableView:willDisplayCell:forRowAtIndexPath: method of UITableViewDelegate, so that is the correct place to do subview customization that relies on the correct frame being set, not cellForRowAtIndexPath:.

Interesting that the docs say this, though:

After the delegate returns, the table view sets only the alpha and frame properties, and then only when animating rows as they slide in or out.

...since the correct frame is already there when this delegate method is called. But anyway, problem solved.

OTHER TIPS

Don't forget that the cell is a UIView, so overriding layoutSubviews is also a valid way to get the correct frame and adjust size/position of subviews. Just don't forget to call [super layoutSubviews].

Easiest way I found was just to call cell.layoutIfNeeded() before you do any setup on the cell. This makes sure all the layout constraints are calculated and the frames are set.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top