Caveats for Self Sizing Cells

I ran into some issues with preferredLayoutAttributesFitting
 / systemLayoutSizeFitting when building my layout. According to this WWDC sessionsystemLayoutSizeFitting spins up an autolayout constraint solver engine. It seems to be a less comprehensive engine than normal though. It has trouble with proportional constraints, and multiline UILabels with automatic preferred widths. It also doesn’t take trait collection size class overrides (from storyboard / xibs) into account until the view has been completely laid out once. Unfortunately, none of this is documented, and I had to debug extensively to nail down the causes and solutions to these issues.

My conclusion is that it’s generally preferable to take a more specific and performant approach for each circumstance, rather than rely on the one size fits all preferredLayoutAttributesFitting / systemLayoutSizeFitting. Let’s go through some scenarios.

When You Have One Dynamically Sized Subview

When Size Class Overrides Are Ignored

The layout engine that systemLayoutSizeFitting uses will ignore size class overrides from storyboards / xibs on the first pass. So it’s not until you scroll away from a cell and back again to force a second layout pass that it has the correct size.

Are the size class constraint overrides that your view requires very complex? As outlined in the above WWDC video, you might be better off splitting your xib into two: one xib for one group of trait collections, and one xib for another. You can register a different xib for the view class depending on the current trait collection. That way no overrides are needed, so the engine will have no troubles and it will also calculate the cell layout quicker.

One thing to note with this approach: you will need to wait for the trait collection to be set on the owning view controller before you register cells. In iOS 13 this changed a little.

When You Use Proportional Constraints

If you rely on proportional constraints for your reusable view and it’s not trivial to calculate the size yourself, you need to get a little more creative.

One approach is to replace proportional constraints with constant constraints the first time preferredLayoutAttributesFitting is called. This may be trivial if your constraint is proportional to the static dimension of the element view, then you can just multiply the multiplier by the static dimension value and set that as the constant.

For anything more complex though, it might also be better to lay out the view in code. That way you have all the constants and multipliers you need anyway, so you can just use constant constraints from the beginning.

When You Have Multiline UILabels

Your collection view then takes the preferred attributes returned by your view and asks the layout if it needs to invalidate for them. The answer is almost always yes if the size in the preferred attributes is different from the size in the original attributes: a new layout will need to be calculated to account for the new size.

Layout Sequence Inconsistencies

In Part 1 I stated that things happen in this order:

  1. Element attributes are asked for
  2. Preferred attributes are asked for
  3. Layout is invalidated
  4. Layout is prepared

But this isn’t always the case. Sometimes it goes:

  1. Preferred attributes are asked for
  2. Layout is invalidated
  3. Prepare is skipped ⚠️
  4. Element attributes are asked for ️⚠️

That means attributes that are calculated in prepare() are out of date, and now your layout looks like 💩. It’s not clear what causes this to happen, and it’s also unfortunately undocumented.

As a workaround, I used a flag to denote the layout data as “dirty” and in need of recalculation. This flag is checked at the start of each layoutAttributesFor(...). If it’s set, the layout data is recalculated and the flag reset before continuing. The flag is also reset at the end of prepare()too of course. This ensures the attributes supplied are always up to date, without any double handling.

End of Part 2

Max Clarke

Tech Lead,Brisbane

Max Clarke

Tech Lead,Brisbane