iosdev

One solution for 90% of Auto Layout exceptions

I dislike these ‘one-really-weird-trick...’ titles but this one is really useful in so many cases. Understand it and apply it diligently.

How many times have you seen this output in Xcode console:

(
    "<NSAutoresizingMaskLayoutConstraint:0x60c00028d5c0 h=-&- v=-&- _UIVisualEffectContentView:0x7faa9f7054e0.width == UIVisualEffectView:0x7faa9f705090.width   (active)>",
    "<NSLayoutConstraint:0x60000028eb50 UIView:0x7faa9f7056e0.trailing == UISegmentedControl:0x7faa9f1607d0.trailing   (active)>",
    "<NSLayoutConstraint:0x60000028ebf0 UIView:0x7faa9f7056e0.leading == UISegmentedControl:0x7faa9f1607d0.leading   (active)>",
    "<NSLayoutConstraint:0x60000028ec90 UISegmentedControl:0x7faa9f1607d0.leading == _UIVisualEffectContentView:0x7faa9f7054e0.leadingMargin   (active)>",
    "<NSLayoutConstraint:0x60000028ece0 _UIVisualEffectContentView:0x7faa9f7054e0.trailingMargin == UISegmentedControl:0x7faa9f1607d0.trailing   (active)>",
    "<NSLayoutConstraint:0x60000028f460 H:|-(0)-[UIVisualEffectView:0x7faa9f705090]   (active, names: '|':UIView:0x7faa9f704eb0 )>",
    "<NSLayoutConstraint:0x60000028f4b0 H:[UIVisualEffectView:0x7faa9f705090]-(0)-|   (active, names: '|':UIView:0x7faa9f704eb0 )>",
    "<NSLayoutConstraint:0x60c00028cdf0 'UIView-Encapsulated-Layout-Width' UIView:0x7faa9f704eb0.width == 0   (active)>"
)

Here’s another, much simpler example:

(
    "<NSLayoutConstraint:0x608000291990 H:[UITableView:0x7f8207122000]-(8)-|   (active, names: '|':UIView:0x7f8206c3f9c0 )>",
    "<NSLayoutConstraint:0x6080002919e0 H:|-(8)-[UITableView:0x7f8207122000]   (active, names: '|':UIView:0x7f8206c3f9c0 )>",
    "<NSLayoutConstraint:0x600000294be0 'UIView-Encapsulated-Layout-Width' UIView:0x7f8206c3f9c0.width == 0   (active)>"
)

Or another variant:

2017-10-11 12:39:44.819705+0200 Stage[7072:165152] [LayoutConstraints] Unable to simultaneously satisfy constraints.
	Probably at least one of the constraints in the following list is one you don't want. 
	Try this: 
		(1) look at each constraint and try to figure out which you don't expect; 
		(2) find the code that added the unwanted constraint or constraints and fix it. 
	(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSAutoresizingMaskLayoutConstraint:0x60c00068cd50 h=--& v=--& Stage.LoadingView:0x7fe7ae499850.width == 0   (active)>",
    "<NSLayoutConstraint:0x600000687b20 UILabel:0x7fe7ae499c50'Please wait...'.centerX == Stage.LoadingView:0x7fe7ae499850.centerXWithinMargins   (active)>",
    "<NSLayoutConstraint:0x600000680960 UILabel:0x7fe7ae499c50'Please wait...'.leading >= Stage.LoadingView:0x7fe7ae499850.leadingMargin   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600000680960 UILabel:0x7fe7ae499c50'Sačekajte...'.leading >= Stage.LoadingView:0x7fe7ae499850.leadingMargin   (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.

I left the hints Apple developers output only in the last example, since it’s always the same text.

So, what’s the issue here and how to solve it?

No, it’s not that…

Tricky red herring:

(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, 
refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 

In my case at least, it’s never that – I have enough experience that I rarely forget to set this:

view.translatesAutoresizingMaskIntoConstraints = false

…it’s actually this

"<NSLayoutConstraint:0x600000294be0 'UIView-Encapsulated-Layout-Width' UIView:0x7f8206c3f9c0.width == 0   (active)>"

plus your constraints. UIView-Encapsulated-Layout-Width and its counterpart UIView-Encapsulated-Layout-Height is an internal UIKit thingie that’s outside your reach. You can’t control it, you can’t access it, you can’t override it.

This is the constraint used by UIKit to control the outside size of the views where you as non-Apple developer work in. For instance on UITableViewCell, where actual width and height is set internally by UITableView. It’s found on their container views, like when you setup your view as say UITableView.backgroundView.

That’s exactly what’s happening here:

(
    "<NSAutoresizingMaskLayoutConstraint:0x60c00068cd50 h=--& v=--& Stage.LoadingView:0x7fe7ae499850.width == 0   (active)>",
    "<NSLayoutConstraint:0x600000687b20 UILabel:0x7fe7ae499c50'Please wait...'.centerX == Stage.LoadingView:0x7fe7ae499850.centerXWithinMargins   (active)>",
    "<NSLayoutConstraint:0x600000680960 UILabel:0x7fe7ae499c50'Please wait...'.leading >= Stage.LoadingView:0x7fe7ae499850.leadingMargin   (active)>"
)

I have a simple view with the UILabel x-centered within margins and with both sides of label set to be >= of its superview horizontal margins.

Those margins are 8pt, so my LoadingView requires at least 16pt wide container, in the edge case where label.text = nil. However, in this particular moment, UITableView has its width set to 0:

<NSAutoresizingMaskLayoutConstraint:0x60c00068cd50 h=--& v=--& Stage.LoadingView:0x7fe7ae499850.width == 0   (active)>

which causes this Auto Layout exception.

Why?

Label’s both leading and trailing constraint have priority = 1000 plus the center-X-to-superview constraint is also set to priority=1000. Thus all 3 must be obeyed, which requires a minimum of 16pt-wide container which can’t fit into 0-width specified by UITableView, hence ka-boom.

“But everything looks fine on the screen, UITableView is not 0-width” I hear you say. Yes, that’s true - at the end of transition that happens while this particular view is being shown. But for way too brief moment, UITableView was 0-wide sometime along the way and that caused the error output.

Solution? Give the Auto Layout some degree of freedom.

By settings both leading and trailing constraint’s priority to 999, you allow Auto Layout engine to temporary ignore the margins.

That’s my greatest Auto Layout trick:

set priority to 999 for half of your constraints in horizontal and/or vertical dimension

Here’s another example, with self-sizing UITableViewCell:

Label showing actual date value is bottom aligned to superview’s margin thus requires at least 8pt at the bottom. It’s also baseline-aligned to the Date label next to it, from where there’s constraint chain that goes up the vertical axis right until the ID label’s top aligned to superview’s top margin. Plus I have set both Date and ID label’s compression resistance to 1000.

When you do insertRows or deleteRows with certain animation types, UIKit will animate the row height from 0 to full height or back. At the 0-end of that animation, the layout equations are impossible to solve if entire vertical axis is set to priority=1000. But lower just one constraint to 999 – say that bottom space to superview margin – and all is fine; the content will just drop-down, outside the cell’s bounds.

Hence the promised one solution to most of your head-scratching Auto Layout issues: for every view you add that has a connection to a superview that you do not control, make sure that one side of that connection has priority less than 1000. I usually set trailing and bottom constraint to 999 + make sure that the said superview has clipsToBounds=true.

Happy layout-ing.