Auto layout best practices for minimum pain

Auto layout is a great tool, it helps keep our sanity as developer, and it prevent us lazy people from using magic numbers when setting frames.

But no technology comes without disadvantages, i have to say that i spent way too much time debugging some missing constraints, or some view deep down the view hierarchy would break the whole layout by adding a conflicting constant, when that happens all hell break lose!

After countless hours debugging auto layout issues every time i discover that what caused the issues was always me (or you!), the solutions to the issues are always the same; to adhere to auto layout documentation and rules!.

I will describe here the best practices to use auto layout correctly and save yourself some pain.

A UIView Subclass should implement intrinsicContentSize

Each UIView subclass should implement the intrinsicContentSize and return the size that it thinks its ok for it.

Lets say we created an AwesomeView, and we know that this view default size is 300x20, we would do this:

- (CGSize)intrinsicContentSize {
return CGSizeMake(300, 20);
}

If we didn’t know how wide the view is we would use UIViewNoIntrinsicMetric instead of the width:

- (CGSize)intrinsicContentSize {
return CGSizeMake(UIViewNoIntrinsicMetric, 20);
}

UIView base implementation of updateConstraints will call the intrinsicContentSize and it will use the size returned from it to add constraints to AwesomeView.

In the above example of (300, 20) size, the following constraints will be added:

<NSContentSizeLayoutConstraint:0x7fef48d52580 H:[AwesomeView:0x7fef48ead7f0(300)] Hug:250 CompressionResistance:750>,
<NSContentSizeLayoutConstraint:0x7fef48d4d110 V:[AwesomeView:0x7fef48ead7f0(20)] Hug:250 CompressionResistance:750>

The constraints added are special ones, they are of NSContentSizeLayoutConstraint type, which is a private classes. These constraints have a priority of 1000 a hug resistance of 250 and compression resistance of 750 and the constant is equal to the values returned from intrinsicContentSize.

Please note that the UIView base implementation of updateConstraints will add the intrinsicContentSize constraints only the first time its get executed.

A UIView Subclass should never add constraints on its size

Every view is responsible about setting the constraints on its superview, however a view should NEVER! set its own constraints, wether these constraints are constraints to self (such as NSLayoutAttributeWidth and NSLayoutAttributeHeight) or they are constraints relative to its super view.

If a view wants to specify its height or width, it should do that by implementing intrinsicContentSize

The following is bad:

- (instancetype)init
{
self = [super init];
if (self) {
[self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:0 constant:100]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:0 constant:100]];
}
return self;
}

This view is settings its own width and height by adding constraints to it, so now what happens when its superview tries to specify them too?

//Some place in the superview
[awesome addConstraint:[NSLayoutConstraint constraintWithItem:awesome attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:0 constant:200]];
[awesome addConstraint:[NSLayoutConstraint constraintWithItem:awesome attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:0 constant:200]];

Boom!

Unable to simultaneously satisfy constraints.

property translatesAutoresizingMaskIntoConstraints)
(
“<NSLayoutConstraint:0x7ff3b16c2ae0 H:[AwesomeView:0x7ff3b16bfa00(100)]>”,
“<NSLayoutConstraint:0x7ff3b16c2330 H:[AwesomeView:0x7ff3b16bfa00(200)]>”
)
Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7ff3b16c2330 H:[AwesomeView:0x7ff3b16bfa00(200)]>

AwesomeView added width/height constraints to 100/100 and its superview also added width/height to 200/200, now auto layout is unsure on which constraints to choose from, since both these constraint have the same priority.

One way to solve this, is by making AwesomeView self constraint to be of a lower priority:

 [self addConstraint:({
NSLayoutConstraint *constraint;
constraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:0 constant:100];
constraint.priority = 800;
constraint;
})];

So now auto layout can make a choice, since the self added constraints are of a lower priority it will chose the superview added constraints.

However although this solved the issue, the correct way for a view to specify its height is through intrinsicContentSize.

A UIView Subclass should never add constraints to its superview

For the same reason above, a subview should never add constraints on its superview. The position of subview its a choice of the superview.

The following is bad:

- (void)didMoveToSuperview {
[super didMoveToSuperview];
[self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
}

Awesome its too awesome to leave its position choice to its parent. so what happens when the superview decides that it wants to place Awesome in another place … yeah another Unable to simultaneously satisfy constraints. will be thrown!

updateConstraints is used to update the constraints

As the name implies updateConstraints is only used to update readily added constraints. A successfully implemented updateConstraints should look like:

- (instancetype)init
{

init stuff

_labelCenterYConstraints = [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
[self addConstraint:_labelCenterYConstraints];
label.text = @”I Am truly awesome!”;

}
- (void)updateConstraints {
self.labelCenterYConstraints.constant = self.labelVerticalDisplacement;
[super updateConstraints];
}

Then later in life:

awesome.labelVerticalDisplacement = 40;
[awesome setNeedsUpdateConstraints];

Calling setNeedsUpdateConstraints will cause auto layout to recalculate the layouts and hence calling updateConstraints, which will read the new label state and update the constraints.

In the above example you could have just updated the _labelCenterYConstraints constraint, if your views expose constraints, or if you could easily grab a constraint, then instead of using updateConstraints just set the constraint constant. So the above could have implemented as:

awesome.labelCenterYConstraints.constant = 40;

A very bad implementation of updateConstraints looks like:

- (void)updateConstraints {
[self removeConstraints:self.constraints];
/*
create the constraint here
*/
  [super updateConstraints]; 
}

This is very wrong for a lot of reasons:

  • The system calls updateConstraints lots of time, hence removing and recreating possibly valid constraints.
  • [self removeConstraints:self.constraints]; will remove any constraint even the ones set by your xib or storyboard, how will you recreated these constraints again? (quick answer you cannot!!)
  • The above updateConstraints implementation will overwrite the effect of intrinsicContentSize, since you are removing the constraints added by the system after calling [super updateConstraints];
  • updateConstraints should be used to create constraints once, and then only removing the constraints that are invalid. it was never intended to be a place where all the constraints are removed and added back on each layout pass. (Thanks to alexis for the follow up ☺)

The correct way to implement it:

- (void)updateConstraints {
if (!didSetConstraints) {
didSetConstraints = YES;
//create the constraint here
}

//Update the constraints if needed
  [super updateConstraints];
}

In the above code, the creation of the constraints happen only once, then in the consequent calls to updateConstraints these created constraints constants are edited.

I am always in the search for truth! so if you think that you have a set of better practices please do share them with me twitter.