I have a UITableView
with cells that are variable in height, laid out with Auto Layout in Interface Builder (rowHeight = UITableViewAutomaticDimension
). When the user rotates the device I perform some manual layout changes inside the cell that affect the intrinsicContentSize
one of the cell's subviews. As these changes are dependent on the initial frame of that particular subview I can only apply these changes once the layout engine has resolved the constraints and applied the correct frames to all subviews for the new (rotated) layout. This happens inside the layoutSubviews()
method. So to apply my manual changes I override that method and first call the super implementation.
override func layoutSubviews() {
// Call `super` implementation to let the layout engine do its job
// and apply the new frames to subviews after resolving constraints.
super.layoutSubviews()
// Apply manual layout changes here
}
Now the problem is that my manual layout changes possibly also change the cell's height. Hence I need a way to inform the table view that a second layout pass is required for this cell before the (automatic) height calculation returns. (Otherwise the table view will just use the row height it computed before the manual layout changes.)
Usually you do this by invalidating the layout with a setNeedsLayout()
call on the view that needs another layout pass. However, I cannot do this from inside the layoutSubviews()
method because it would result in an infinite loop of layout passes and crash the app.
So I tried to figure out a way if the layout had changed in this particular call of layoutSubviews()
and came up with this approach:
override func layoutSubviews() {
// Get old image view size before layout pass
let previousImageSize = imageView.frame.size
// Run a layout pass
super.layoutSubviews()
// Get the new image view size
let newImageSize = imageView.frame.size
// Only trigger a new layout pass if the size changed
if newImageSize != previousImageSize {
// Apply manual layout changes here
// Trigger another layout pass
setNeedsLayout()
}
}
(In this example, the imageView
is a subview of the cell on which my layout depends.)
When running this code I realized that the code inside the if
branch is never executed, i.e. the imageView
size never changes inside the layoutSubviews()
method. But it does change - only at a different time.
That's inconsistent to my understanding Auto Layout in general:
A view is always responsible for laying out its subviews, i.e. for setting their frames appropriately and this is done in
layoutSubviews()
.
So here are my questions:
- Why does the system change the image view's size from outside the
layoutSubviews()
method?
and more importantly:
- How can I get the table view to run a second layout pass when computing the row height for a cell?