Learn to love Auto Layout… programmatically

Introduction

Yari D'areglia

The last months were quite intense. Nicola and I, have released an iOS App for a client that right now is heavily featured in the Italian App Store, I’m planning a trip to Japan (actually, my girlfriend is…) and I’ve started a new personal iOS project.

Nonetheless, I’m ready to continue on our journey with Auto Layout!

In the previous article I introduced the topic with some easy examples (please, if you are new to Auto Layout, read that before proceeding).

Intro

In this new post I want to show you how to use Auto Layout programmatically. It means that we are not going to use xib or storyboards… just code.

Before opening xCode let’s quickly introduce the Visual Format Language (VFL) and the functions needed to manage the layout.

With VFL you can define constraints using a simple syntax. For example you can define the width of an element using this string:

"H:[element(100)]"

or the height using:

"V:[element(100)]"

The first uppercase char tells which dimension you want to modify. H stands for horizontal and V for Vertical. Then you define the constraints (more about that in the next examples).

Constraints are defined by the NSLayoutConstraint class. You can attach new constraints to a view with UIView’s method addConstraint(s): and remove them with removeConstraint(s):

Let’s code

Download the project and open the file viewController.m.
This file contains all the code for this tutorial and it is organised in single autonomous functions (with a lot of repeated code! F@*& you DRY -.-). Each function represents an example.
You can activate the examples from the viewDidLoad function.

The main view contains 2 subviews: redView and yellowView. In the next examples you are going to place these views into the bounds of the main view using Auto Layout only.
The methods setupViews is the place where the views are initialised and configured:

Really important: when you have to deal with Auto Layout programmatically you should turn off translatesAutoresizingMaskIntoConstraints. This ensures no constraint will be created automatically for the view, otherwise, any constraint you set is likely to conflict with autoresizing constraints (when you add constraints from IB it automatically sets that property to NO).

EX1: Simple constraints with VFL

Great! you are ready to get your hands dirty.
Go to function example_1. This code just adds a square in the top left corner of the main view, like in the next image.

1. First, create a dictionary that associates keys to any view that you are going to use in VFL definitions.

NSDictionary *viewsDictionary = @{@"redView":self.redView};

In this case we’ve marked a reference to the redView view using the key “redView”.

2. Time to create the first constraints with the method constraintsWithVisualFormat:options:metrics:views: of NSLayoutConstraint class.

This method returns an NSArray of constraints. Depending on the VFL you pass to the function it creates one ore more constraints.

In this example we shape width and height constraints for redView using this code:

Let’s start by analysing the visual format strings.
As previously shown, VFL uses H or V to define the orientation of the constraints. Then the square brackets enclose a reference to a view and the parentheses contains the value for the attributes we are setting (depending on V or H we set width or height).
We use “redView” to point out the redView view because we have previously defined a key for it in viewsDictionary and we pass it to the views: argument of the functions.

Now that we have defined constraints for the size we attach it to the redView:

3. To correctly define the constraints for a view you need to give to Auto Layout enough information to obtain size and position. We have already given information for the size of redView, now let’s place it in the main view bounds. Again, we use the previous function just changing the visual format string.

Let’s translate the first Visual format string in a simple English sentence. @”H:|-30-[redView]” stands for “RedView must maintain a distance of 30 points from the left side of its superview”
While writing the string this way @”H:[redView]-30-|” we say the distance has to be calculated from the right side of the superview.
The pipe char “|” can be read as a reference to the “superview” of the object between square brackets.

The same is true for the Vertical orientation, this time a pipe on the left means “top” while on the right means “bottom” side, so we write:

and this code is defining that the redView must keep a distance of 30 points from the top side of its superview.

You could also let the system use default spacing creating a VFL string like this:

@"V:|-[redView]"

No numeric info are given in this string. iOS will use a default distance. Just remember that the syntax needs the “-” to separate the pipe and the element.

Now we need to attach these last constraints to a view to let them take effect.

The view to receive this constraints is the main view. Attention though, we are not attaching the constraints to the redView. To easily remember how to attach constraints, just remember that it is responsibility of the parent view to assign position to its children. So in this case, the main view receives the constraints to arrange redView within its bounds.

We are using the same function, so just focus on the VFL string @”H:|-20-[redView]-10-[yellowView]”. Translating it to plain english we’ll end up with more than one constraint:
– redView has a distance of 20 points from the left side of its superview
– the left side of yellowView is 10 points distant from the right side of redView

Does it make sense? I order to help you read these strings you should keep in mind that they are based on really simple sequences where the main actors are the “|” and the views between “[]”. All the information that separates the actors create constraints.

And to build vertical information we write:

@"V:|-30-[redView]-40-[yellowView]"

This string says
– redView has a distance of 30 points from the top side of its super view
– the top side of yellowView is 40 points distant from the bottom view of redView.

EX3: Simple constraints with VFL using alignment options

The previous example places the views in a messy way. Let’s rearrange them in a cleaner layout. For example we could align the views to their top sides obtaining this result:

Achieving that is surprisingly easy! We just call the functions constraintsWithVisualFormat:options:metrics:views specifying a value for the options parameter. In fact this parameter stands for alignment-options (at least at the moment).

Let’s check the VFL string: @”H:|-20-[redView]-10-[yellowView]”. It just shapes the horizontal behaviour as it did in the previous example, so nothing new here. The Options parameter though gets the value NSLayoutFormatAlignAllTop.
Within this function call we are defining constraints for both dimensions though, and moreover we are doing it thanks to a relation between the views cited into the VFL string.

CMD+Click NSLayoutFormaAlignAllTop and you’ll se all the available options. We could obviously create a VFL string for vertical orientation which together with an horizontal alignment option (like NSLayoutFormatAlignAllLeft) gives enough information for vertical and horizontal positioning.

EX4: Simple constraints with VFL and metrics

Ok we are almost done with the function constraintsWithVisualFormat:options:metrics:views. We’re left with defining what the metrics parameter is.

In step 1 of the fourth example you’ll see another NSDictionary defined.

It works exactly as viewsDictionary. IT defines a key for any value that we want to use in the VFL string. Without further ado I can write some of the VFL strings that are going to use these keys. You can easily get that metrics help you writing more readable VFL strings.

Let’s check the string for the horizontal data. @”H:|-hSpacing-[redView]-hSpacing-|”

We simply define both margins to the left and to the right of redView in relation to its superview. Nothing more. You can check the result compiling and changing your device orientation.

EX6: Defining constraints through relations

Another really cool feature of Auto Layout is the ability to edit an attribute of a view in relation to an attribute from another view. With this kind of interaction between views you can achieve really complex behaviours.

The function that lets you easily create these relations in constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant: (from now on I’ll just call it The_function :P) of NSLayoutConstraints.

Thanks to The_function you can create constraints like this:
“redView width has to be the half of yellowView height plus 20 points”.

In example 6 redView behaves just as it does in the previous example, while yellowView has to be the half of the redView size and it has to be centred in redView. Here’s the result:

First as for the previous examples, we attach the positioning constraints to the main view. This function just builds a constraint like:

yellowView.width = redView.width * 0.5 + 0.0;

It uses an equality relation (NSLayoutRelationEqual) to associate a value to the yellowView width (NSLayoutAttributeWidth) starting from the redView width multiplied by the multiplier (0.5) and adding a constant value (0.0).

The height constraint is built in a really similar way, it just needs different values (NSLayoutAttributeHeight) as attribute parameters:

We could also build constraints based on other relation types (NSLayoutRelationLessThanOrEqual and NSLayoutRelationGreaterThanOrEqual) and linking many other attributes (CMD + click on one of the attributes previously used in the functions).

While if we want to build constraints with no relation simply by specifying a constant value for a single view, we can pass nil as second item and NSLayoutAttributeNotAnAttribute as second attribute, then specify the constant we need.

Good night

Great! This is everything I wanted to cover in this short Auto Layout series, but since it is a really important topic, here are some other resources you should take a look at: