Mastering programmatic Auto Layout

without going insane…

jonathan sleeuw
7 min readApr 16, 2014

--

While Xcode 5 brought much needed improvements that allow the developer to visually configure their layout constraints without completely losing their marbles, situations can arise that require constraints be defined in code. There are those developers, myself included, who prefer to avoid some of the unpleasantries of working with XCode’s visual bits and maintain their projects without Interface Builder.

In this post we’ll look at the programmatic interfaces available for Auto Layout in iOS 7 as well as and open source alternative called Masonry.

A Sample Layout

For all of the examples that follow, we’re going to create a layout that a) centers a label horizontally, and b) positions the label 100 points from its containing view’s top edge. As an additional challenge, we’ll also look at how we might go about animating the position of the label in the containing view.

Programatic Layout using the Visual Format Language

We’ll follow the approach in Apple’s Auto Layout Guide — Working with Auto Layout Programatically, and get started with the Visual Format Language.

Layout

Here’s the code we’ll use to define the vertical constraints (Gist):

UILabel *label = [UILabel new];
label.text = @"This is a Label";
label.font = [UIFont systemFontOfSize:18];
label.backgroundColor = [UIColor lightGrayColor];
label.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:label];

NSArray *constraints = [NSLayoutConstraint
constraintsWithVisualFormat:@"V:|-offsetTop-[label]"
options:0
metrics:@{@"offsetTop": @100}
views:NSDictionaryOfVariableBindings(label)];

First thing to observe is translatesAutoresizingMaskIntoConstraints being set to NO, which you’ll need to do to all views you plan to auto layout in code.

Let’s take a look at the arguments we’re passing to constraintsWithVisualFormat:options:metrics:views:, in reverse order.

views: is passed a dictionary that consists of: keys that you’ll embed in the format string; and values that are the view objects being targeted for layout. In the example above we are using a convenience function that creates such a dictionary from a list of variables.

metrics: the metrics dictionary provides a set of strings that can be interpolated from the format string. You’d rarely use this with such a trivial layout, but with more complex views it can be a helpful to have often referred to values kept in one place.

options: gives you an opportunity to specify alignment attributes for the views you are laying out. For example, vertically stacking a group of buttons such that their left edges align can be accomplished by passing an NSLayoutFormatAlignAllLeft option to this parameter.

format: this is where the layout logic is defined. I’ll spare a lengthy a description of all available options and just explain what we’re doing here:

V: means we’re targeting the vertical axis. (| pipe) references our superview. -offsetTop- puts 100 points (substituted in from our metrics dictionary) between the superview and the view that follows. [label] defines where our label should appear.

The horizontal constraint is a little more involved:

UIView *spacer1 = [UIView new];
spacer1.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:spacer1];

UIView *spacer2 = [UIView new];
spacer2.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:spacer2];

[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"H:|[spacer1][label][spacer2(==spacer1)]|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(label, spacer1, spacer2)]];

Yes, those spacer views are there just for positioning, and yes, it does look like a hack, but it is actually a method that Apple recommends for creating equal spacing between views.

As for what the format string accomplishes; the spacers are instructed to hug the left and right edges of the superview with the label in between. The centering effect is achieved by having the width of spacer2 be equal to that of spacer1. The label’s intrinsic size ensures that it will only occupy the horizontal and vertical space that it needs.

You may have noticed our constraints being returned as arrays. This highlights one of the conveniences of using the Visual Format Language: many constraints can be created from one method call. But it also introduces difficulties when you need to reference a specific constraint, when animating, for example.

Animation

Let’s take a look at how we might go about finding the constraint used to position the label top, and update it such that its position will animate downward.

[self.view.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
if ((constraint.firstItem == label) && (constraint.firstAttribute == NSLayoutAttributeTop)) {
constraint.constant = 200.0;
}
}];
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
}];

There’s certainly a little bit more hoop jumping going on here than one would like, and while atCategory might help to hide some of this, you’re still left feeling that there has to be a better way.

The Visual Format Language does offer a reasonably concise way to define your layouts, and once you get your head around its workings you can become quite productive with it. It’s not without its limitations though; we’ve had to resort to a hacks to do things you’d expect to be able to do right out of the box (centering a view in its superview), and it would seem not to have been designed with maintaining references to individual constraints in mind.

Standard Programmatic Layout

We’re going to change our sample slightly to demonstrate a layout case that cannot be solved directly with the Visual Format Language. Rather than our label being fixed 100 points from top, let’s position it relative to the height of our superview, say, 25% from the top.

Layout

Here’s how we’d go about building this layout using the standard / non-visual syntax, starting with the vertical (Gist):

@interface ViewController ()
@property (strong, nonatomic) NSLayoutConstraint *constraintToAnimate;
@end
...
UILabel *label = [UILabel new];
...
label.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:label];
self.constraintToAnimate = [NSLayoutConstraint constraintWithItem:label
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:0.25
constant:0.0];
[self.view addConstraint:constraint];

The first two parameters determine the target of the constraint, label, and the attribute that is to be set: top. The third parameter, relatedBy, accepts one of NSLayoutRelationEqual, NSLayoutRelationGreaterThanOrEqual, or NSLayoutRelationLessThanOrEqual. The next two parameters allow us to set the source view and attribute from which we’ll derive a value. Here we are saying that we want to use the bottom point of our superview, which would be 568 points on a 4-inch device. Lastly we’ve got a multiplier that we’ll use to derive our value — 0.25 (25%). We’re not using the constant parameter, so we’ve set it to 0.0. Put into a mathematical equation it would look something like this: label.top = superview.bottom*0.25.

You’ll notice that this method returns individual constraints, which affords us the opportunity to assign the result to a variable that we’ll use later, for animation.

As for the horizontal constraint:

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];

Here we’re just saying that the label’s center point X should be identical to that of its superview. Notice that we’re not having to fudge things into place with spacer views!

Animation

Had we set up our label top constraint with a constant rather than a multiplier, then our animation would have been a relative snap:

self.constraintToAnimate.constant = 200.0;
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
}];

We’ll continue working with relative positions though, so let’s animate the label to a position 50% from top. Given that NSLayoutConstraint’s multiplier property is read only, we’re forced to remove, redefine, and then re-add it, which we’ll do as follows:

[self.view removeConstraint:self.constraintToAnimate];self.constraintToAnimate = [NSLayoutConstraint constraintWithItem:label
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:0.5
constant:0.0];
[self.view addConstraint:self.constraintToAnimate];

[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
}];

There are two important benefits that we’ve been able to highlight here, first being that it’s much easier to maintain references to the constraints we create, and second, that we’re able to use multipliers to define layout attributes relative to other views. However, and it may not be obvious from these simple examples, but this code will soon start to pile up as you add more views, and with them more constraints. As a result, it’s very hard to make a case for using this method alone to generate complex layouts.

Hello Masonry

As I started putting thoughts together on how to describe using Masonry, its syntax etc., it occurred to me that there’s really very little to say because it does such a good job of explaining itself.

Here’s how Masonry describes the fixed layout we’ve been working with:

UILabel *label = [UILabel new];
...
[self.view addSubview:label];

UIView *superview = self.view;
[label mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(@100);
make.centerX.equalTo(superview);
}];

In just two lines we’re able to clearly declare our layout. A couple of other points worth mentioning here; Masonry disables translatesAutoresizingMaskIntoConstraints for us, so there’s no need to keep setting it, and it also takes care of adding the resulting constraints to the appropriate views. Very nice.

In order to position our label’s top edge relative to its superview we’d do the following:

[label mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_bottom).multipliedBy(0.25);
make.centerX.equalTo(superview);
}];

You can see the chainable syntax really helping to improve readability here.

Animation

In the previous examples we’ve needed a reference to our constraint to carry out animations. The same applies here, so let’s modify the fixed layout example from above accordingly:

@interface AutoLayoutMasonryViewController ()
@property (nonatomic, strong) MASConstraint *constraintToAnimate;
@end
...
[label mas_makeConstraints:^(MASConstraintMaker *make) {
self.constraintToAnimate = make.top.equalTo(@100);
make.centerX.equalTo(superview);
}];

And here’s how we carry out the animation:

self.constraintToAnimate.offset(200);
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
}];

Our constraint make expressions pass around MASConstraints, or subclasses thereof, so rather than updating a constant here we are modifying an offset. Of course, this does ultimately result in Masonry carrying out operations on an NSLayoutConstraint instance.

Animating our relative layout is slightly more involved:

[self.constraintToAnimate uninstall];
[self.label mas_updateConstraints:^(MASConstraintMaker *make) {
self.constraintToAnimate = make.top.equalTo(superview.mas_bottom).multipliedBy(0.5);
}];
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
}];

As in the standard programmatic interface example, we have to remove and recreate the top constraint, but here we’re doing so using methods provided by Masonry.

If we were handing out awards here, Masonry would be the clear winner. It is capable of handling the advanced layout cases that would typically require the verbose standard programming interfaces, yet it’s clean and intuitive layout declarations make it a far more convenient option than the Visual Format Language.

Auto Layout is a very powerful tool, but the standard programming interfaces are way off the mark when it comes to expressing your intentions in code. This is where Masonry really steps up — with it you can really take command of Auto Layout, take on the problems that it was designed to solve.

--

--

jonathan sleeuw

iOS and web developer located in Kyoto. Lead developer at @SippApp.