2

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:

  1. Why does the system change the image view's size from outside the layoutSubviews() method?

and more importantly:

  1. How can I get the table view to run a second layout pass when computing the row height for a cell?
Mischa
  • 15,816
  • 8
  • 59
  • 117
  • I think you you are looking at it the wrong way. If you want cells with dynamic heights you might want to look into this tutorial. https://www.raywenderlich.com/129059/self-sizing-table-view-cells – Totumus Maximus Jan 23 '17 at 14:29
  • What is the right way then? My question here is not how to get self-sizing table view cells with Auto Layout in general. The question is how to get properly sized table view cells (or rows) in cases where you need to *modify* the cell's layout and you can't use constraints for that. – Mischa Jan 23 '17 at 14:35
  • 1
    Well based on the Apple documentation: https://developer.apple.com/reference/uikit/uiview/1622482-layoutsubviews. You can either use setNeedsLayout() to force a layout update prior to the next drawing update or layoutIfNeeded() to force the update immediately. But I assum you already read this? – Totumus Maximus Jan 23 '17 at 14:51
  • Yes, I guess I'm pretty familiar with the layout process in general. As far as I know when you invalidate the layout during a layout pass (i.e. inside 'layoutSubviews()') by calling 'setNeedsLayout()' another layout pass is triggered which is why I put it in the if branch above to avoid a loop. – Mischa Jan 23 '17 at 15:21

0 Answers0