By now, you probably think you are very proficient at AutoLayout, and you are certainly right. However, to be a complete AutoLayout ninja, you need to master NSLayoutConstraint. In the 4th episode of “The Ultimate Guide to iOS AutoLayout”, we are demystifying NSLayoutConstraint.
In the previous episode of the series, you learned about size classes, and how to build adaptive interfaces for all of your iOS Devices. We also learned that sometimes it’s necessary to “soil your hands” by modifying constraints programmatically.
In this episode, you’ll learn everything about NSLayoutConstraint.
Is it necessary to know how to manage constraints programmatically? Absolutely.
Some time ago, I worked in BioProfe, an application that displays exams with exercises for students. It includes a series of complex formulae that need to be read from streams and displayed.
I had to position all the elements contained in the formula in an AutoLayout environment. Take into account that a division, for example, may have infinite levels of vertical layers top and bottom. Also, users were supposed to interact with some of the elements (i.e: buttons with unknown values). Furthermore, StackViews didn’t exist at the time.
I wouldn’t be able to solve this situation by just defining constraints in IB and adjusting some constants. I needed to deeply understand how to build a set of constraints in code.
You might find yourself in front of a similar challenge. A good knowledge of NSLayoutConstraint will be essential in these cases.
NSLayoutConstraint is the class representation of a visual constraint from IB (actually, the opposite is more correct). You can think on it as an object that relates an view with a dimension (height, width) or another view, via a property (top, center, height…).
A NSLayoutConstraint, conceptually, has the following properties:
Don’t worry if this seem like a lot of information. You’ll understand it easily with some examples:
First, this example will show you how to define a constraint that only affects one view. Let’s say that we want to set the width of a constraint to 150px. We will create an NSLayoutConstraint like this:
NSLayoutConstraint(item: ourView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 150)
We could describe this definition as:
create a constraint for ourView to set its width equal to a fixed attribute of 1 x 150px.
Notice how we did set “toItem” as nil, as we are not relating this view with any other view.
Let’s say we want to specify that a view’s width has to be set to twice the height of another view. The following figure describes this scenario.
The correct way of defining this constraint will be:
NSLayoutConstraint(item: view2, attribute: .width, relatedBy: .equal, toItem: view1, attribute: .height, multiplier: 2, constant: 0)
Note that we are not defining the rest of constraints for these views. Thus, the rest of properties for the views that appear in the figure should need to be defined too.
Now that we know how to define constraints programmatically, we need to how to add those constraints into our view hierarchy. We add individual constraint to a view by means of the addConstraint: method of UIView.
The rule of thumb is: constraints that affect only one view are added to that view. Constraints that affect two or more views need to be added to their parent view.
Let’s see how the constraint of the first example could be added to its view:
let c = NSLayoutConstraint(item: ourView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 150)
Now, let’s see how we would add the constraint to view1 and view2 in our second example. Let’s suppose that both are
let parentView = UIView(...)
let view1 = UIView(...)
let view2 = UIView(...)
let c = NSLayoutConstraint(item: view2, attribute: .width, relatedBy: .equal, toItem: view1, attribute: .height, multiplier: 2, constant: 0)
Of course, this means that two views cannot be related via a NSLayoutConstraint unless they belong to the same view hierarchy’s layer (i.e: they have same parent view).
The visual format is an additional way of defining constraints for a view. It’s power lies in the fact that you can easily define multiple constraints with a simple sentence. This visual language is nothing more than a string following a set of conventions.
To define constraints with the visual language, we use the NSLayoutConstraint.constraints(withVisualFormat:options:metrics:views:) method from NSLayoutConstraint. This returns an array of instances of NSLayoutConstraint, that we can add to a view by means of the view.addConstraints(…) method.
constraints(withVisualFormat:options:metrics:views:) needs you to specify the views that are referenced in the visual format as a dictionary. This dictionary will match the “name” (string representation) of the view and the UIView object, so when you refer to one of those views by its name in the visual format string, the system will know the corresponding UIView’s object.
Let’s see an example:
NSLayoutConstraint.constraints(withVisualFormat: "V:|-[myview]-|", options:, metrics: nil, views: ["myview": myView])
Here, the visual format is “V:|-[myview]-|”. First, you see a [myview] annotation, that points to a view called “myview”. In the “views:” section we have a dictionary where we specify that this annotation refers to “myView”. myView is an instance of UIView. Thus, each time [myview] appears in the visual format, we are defining a constraint that affects myView.
Hence, let’s see the basic rules of the visual language:
Let’s see some examples taken from the Apple visual format appendix:
First, a button and a textfield separated by the standard distance (8px):
A button with a width of 50 pixels or more:
A view that’s positioned exactly 50px to the left and right edges of its superview.
Next, two textfields separated 10px vertically:
Two views that are one at the left of the other, with no separation between them.
Then, a width constraint for a button with a priority value of 20:
Two buttons that have the exact same width:
A button that has a width greater or equal to 70 and less or equal than 100.
Finally, three elements in a row. A “find” button, a “Find next” button and a textfield. The two buttons are separated by the standard 8px, while the button and the textfield are separated by 20px. The first button pins to the left edge, and the textfield pins to the right edge of its superview.
Remember how we learned that constraints that relate two or more views need to be specified in the parent view? This applies to the constraints defined using a visual format string. This is because NSLayoutConstraint.constraints(withVisualFormat:options:metrics:views:) returns an array of constraints.
Now, let’s see how to properly define and add constraints to a view in visual format:
let containerView = UIView()
let upperFill = UIView()
let leftFill = UIView()
let rightFill = UIView()
containerView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[left]-[upper]-[right]-|", options: , metrics: nil, views: ["upper": upperFill, "left": leftFill, "right": rightFill]))
This constraint distributes the views in a row pinned to the edges, left to right, in its superview, containerView.
First, we define our containerView, that is the common superview for the other views. Then we add those views (upperFill, leftFill and rightFill) to containerView. Finally, we define and add the constraints visually to the containerView view. That is the superview of all the views included in the visual format.
You’ll notice how the views’ dictionary contains the names for the views and their references. The first one is “upper” for the upperFill view. Thus, in “H:|-[left]-[upper]-[right]-|”, “[upper]” refers to the upperFill view.
For most simple applications, you will probably be able to define your user interfaces in IB. However, there are situations in which a more specialized approach is required.
Usually, when you need to add elements to a view dynamically in runtime, you will need to use the techniques we learned today. Some use case scenarios are:
In this episode of “The Ultimate Guide to iOS AutoLayout”, we explored the possibilities of NSLayoutConstraint. This class allows us to define constraints programmatically. This is a powerful feature, specially when using the visual format, and allows us to define pretty complex sets of constraints.
Defining constraints programmatically will allow you to address some AutoLayout configurations that need to be defined or grow dynamically.
In the Digital Tips list, I will also share with you some tips to magically align views vertically or horizontally within a superview.
In the next episode of the series, we’ll learn how to properly animate views in an AutoLayout world.