The Ultimate Guide to iOS AutoLayout (I): Mastering the Basics, Ambiguity and Conflicts.March 27, 2017 in
This is a series of tutorials to help you become an AutoLayout expert. In the first episode, we will start by mastering the basics. AutoLayout is a set of rules that dictate how the visual elements of an iOS App display on iOS devices. These rules can be defined visually or programmatically, and the developer needs to define them for every single control or visual element included in the App. Sometimes, defining these rules is pretty straightforward. Other times, an application with a complex interface may need a lot of tweaking, specially if it is available for iPad, iPhone, Apple TV or even Apple Watch.
Users of mobile apps today (specially iOS App) have really high expectations when it comes to the user interface and visual element of their apps. We’ve got used to perfect applications with a great user experience. If one of the buttons of your application is hidden (because we positioned it wrong for some device) for some users, they will uninstall your application. If some of the elements overflow outside the margins of the screen because you set their size incorrectly, the application will be perceived as “amateurish” and, thus, uninstalled. That’s why it’s so important to present the user with an awesome interface, on every device. That’s what AutoLayout is for.
As I discussed in this article, before AutoLayout, we used “Springs and Struts” to define the positions for the different visual elements, and how those elements were allowed to “grow” or “shrink” in relation to other visual controls. This was OK when we only had the original, 3.5” screen iPhone. However, the introduction of the iPad and later the 4” screen iPhone 5, the screens types started to grow in size and aspect ratios. “Spring and Struts” were no longer enough, so Apple introduced AutoLayout, a more mature system mimicking the Layout rules from Android. Later, with the iPhone 6 and 6 plus, and the Apple Watch, Size Classes were added to make our work easier when defining our user interfaces.
Building modern, adaptive interfaces
With AutoLayout, and thanks to Size classes, we can completely define how our interfaces will look on every device. Far from “predicting” how our screens would look like, like we did with “Springs and Struts”, we are now able to precisely define the final aspect of our interfaces.
To understand the difference between setting the elements without or with AutoLayout, let’s have a look at what happens if you position an element in the exact middle of the screen of an iPhone 7. As you might see in this video, when you change the orientation or the device, the element is no longer positioned in the center, even getting to the point of disappearing completely in some configurations:
This is because when you don’t set any rules, the elements’ positions are set as absolute offsets from the origin points, so changing devices with different sizes cause them to distribute unevenly.
Now let’s add a pair of rules to center this label horizontally and vertically. Notice how now the label stays in the center, no matter what device or orientation we choose.
Really nice, right? Mastering AutoLayout rules is of vital importance to build professional user interfaces.
How To Set AutoLayout Constraints
There are many ways of defining the rules (also called constraints) that will dictate the visual aspect of our views and controls. We can define them visually with Interface Builder in Xcode. We can also define it programmatically thanks to the class NSLayoutConstraint. Even though defining them visually will probably suffice for 90% of your applications, and the Interface Builder is a great tool that allows us a great deal of flexibility, sometimes we will need to define them in code. In this initial tutorial, we will learn how to define them visually using Interface Builder.
To access the Interface Builder, you just need to click on any “visual” file in your project. Visual file types are those that contain a description of the visual elements of a screen or set of screens, and are basically two:
- A Nib/Xib file: this is a file that contains an XML specification of a class with a visual representation, like a view (UIView), a table view (UITableView) or one of its cells (UITableViewCell).
- A Storyboard: a storyboard is also an XML file but, this time, the information contained therein specifies a set of view controllers (UIViewControllers) and the segues between them (UISegue). You can think of an storyboard as a group of Nib files put together in the same file alongside a screen flow from view controller to view controller.
The Interface Builder parses these XML files and translates the XML elements into a concrete visual representation. What you see is this representation, depicted visually, allowing you to easily add, remove and modify its elements.
As the figure above shows, you can define and modify AutoLayout rules in the bar at the bottom. The elements that you see on the left allow you to change the device simulated in the storyboard or Nib file, so you can easily see how your controls will distribute in any iOS device on any orientation. On the right, you have five buttons to add and control the AutoLayout rules for our elements. Remember that any of these 5 elements will act on the element that’s currently selected (and all of its subviews), so you must first select the element that you want to set the rules for. These buttons are, from left to right:
- The “Update Frames” button: this button will refresh the selected views by applying all the current rules defined for this them. See the next section for an explanation of why you might need to “refresh” what you see on screen.
- The “Embed in Stack” button will group a set of views you have selected into an horizontal or vertical stack. We’ll talk about stacks in another tutorial. You won’t need to use this button for now.
- The “Align” button will allow you to set rules about how some views’ center or edges relate to the edges or center of its superview or other views. You can use these rules to say a view’s left edge must align to another view’s left edge, or that two views should have the same center vertically and horizontally (which, incidentally, allows you to center a subview inside its parent view). Align operations on views usually require two or more views.
- The “Add New Constraints Button” (the one that looks like a star wars spaceship): allows you to set absolute positioning constraints of a view in relation to other views or its superview, specifying that a view must be pinned to the top, bottom, right or left of another view, and also set its width, height or aspect ratio.
Notice the “constraint to margins” option that’s selected by default. Every view controller has some margins defined in the edges: top, bottom, left and right. These default margins are set to give some space to the visual elements so they don’t appear too close to the edges.
- The “Resolve AutoLayout issues” button allows you to perform automatic operations on some views and their constraints. You can automatically add or update the constraints for the selected views, move the views to where they should be based on their current constraints, or remove them altogether.
To Automate Or Not To Automate, That’s The Question
The “Resolve AutoLayout issues” button needs some further explanations. The “Add Missing Constraints” option can be selected to add all the constraints that will suffice to define the visual aspect of a group or all the views of a view controller. However, these constraints will be automatically “inferred” from the current position of the views and the characteristics (size, aspect ratio) of the device that’s being currently simulated.
That means that, even though you add some views to the view, click “Add missing constraints”, and all the views suddenly become magically aligned, the inference may misinterpret your intentions, and you could end up with some undesired results.
As an example, imagine that you set some views in a simulated iPhone screen, and position one of them in the center of the screen because you want to appear it completely centered. The Interface Builder engine might infer that, instead of centered, the view should be below the view that’s on top of it, at a distance of 10 points, because, apart from being in the center of the screen, it happens to be in that position relative to the view on top. However, you wanted it to be centered, so it will appear differently on a bigger screen, for example, an iPad.
So, my suggestion will be: don’t use “Add missing constraints”, and if you do, check that the constraints that have been added are the ones that you wanted in the first place.
Ok. Now that we understand how AutoLayout works, let’s start by learning to correctly position our views.
Positioning constraints in AutoLayout are used to define where our view should be positioned in its superview. The final aspect of the view will depend on its positioning and its size, but as many view types (such as labels) have intrinsic sizes, it’s usually enough for them to be positioned. We can also define the size via positioning. This happens if I, for example, position a view so that it touches the four edges of the screen: in this case, its size will equal the screen’s.
Absolute positioning: pinning
Pinning allows us to specify the exact place where a edge of a view should “stick to”. In other words, we can say: “The left side of my view should always stay 8px away from this other view”. We can repeat this process with every edge of every screen.
Relative positioning: aligning
Aligning implies relating the edges of two views, or their horizontal/vertical centers. You can align, for instance, the leading (left) edges of two views, so they will always stick together at the left. Their positions and sizes however will be defined by the rest of the constraints you define for them.
Positioning in action
The following video shows how we can position (pin) several elements inside the main view of our view controller, using different AutoLayout methods:
Let’s analyze them all:
We first add a label to the top left corner and extend it to the right corner. In this case, we use the “Add New Constraint” Button we talked about earlier. Remember about the margins? You can see them here in action. You will notice that if we set the “constraint to margins” option, the left/right edges offset is set to zero. This means zero pixels from the margins. Those margins happen to be at 16px off the edges, so if you deselect the “constraint to margins” option, you get the “absolute” values from the edges, which are 16 px each.
So, should you use the “constraint to margins option”? Generally speaking, is a good idea, but take into account that view controllers can be embedded in views inside other view controllers, and in that case the margins no longer apply, so you might want to experiment with what’s best for you. I usually never constrain my views to the margins, and opt for absolute positioning always.
Second, we add a button and center it both horizontally and vertically. This time, instead of using the “Add New Constraint” button, we are going to use a different way of setting the constraints. We can set a constraint from entity A to entity B by holding down Ctrl key and then clicking entity A and -while holding Ctrl-, drag to entity B and then drop. In this time, we want to center the button in its superview (that happens to occupy all the screen. You should be able to set the “Center horizontally” constraint, and then repeat the process for the “Center vertically” constraint.
Third, we add a textfield to the bottom left corner. We drag and drop our UITextField, and then we use a different approach to set the constraint. Notice that the view hierarchy is always depicted in the menu at the left of the Interface Builder. Those views are the equivalent to the ones displayed in the screens, so you can do the same Ctrl+Drag and Drop operations, like in this example. PRO TIP: If you click Cmd while you choose a constraint, like in the video, you will be able to multi-select a set of constraints and then click on “Add constraints” to add them all at once, saving some steps.
Last but not least, we add a UISwitch at the bottom right corner. This time, we ignore the “Constraint to margins” option to be able to put the switch closer to the edges. This example shows you that you can place the offsets that you feel appropriate for your user interface, even negative ones (in which case, the switch will get outside of the boundaries of the screen).
Alignment in action
Now let’s see a simple example of alignment. We will align the button’s right
Notice how, as we specified that the button should be centered, the left (leading) edge automatically grows till reaching the left edge of the label, even if we didn’t set this constraint. You must be aware of this collateral effects when defining your constraints, specially when aligning edges. Notice also how we
Sizing means defining constraints that will set the size (width and/or height) of our views, either directly, or indirectly. Let’s talk about different ways of defining sizes for our controls:
Setting a constant width and/or height is the most easy and usual way of defining the size of a control. Here’s an example on our bottom left textfield:
However, constant sizes in AutoLayout have disadvantages, specially for universal apps. While they are ideal for controls where you have a concrete size in mind (like a button, for instance) that will work for all devices, oftentimes you will need to set relative sizes to make your interface responsive along different devices.
When you add a label (UILabel) to a view, you will see that the label has a size corresponding with the length of the text it contains. If you pin it somewhere, and resize the label, you will notice that Interface Builder indicates the initial size of the label as the correct one, unless you add a width constraint to the label. Why is that? All user interface elements have an intrinsic size, that is the default size that the element adopts in absence of a constraint that defines it.
In the case of a label, its intrinsic size is defined by the text and the font of the label. If you change the text, or increment the font size, the label will grow in intrinsic size. Some other views (like UIView) don’t have an intrinsic size, which means their intrinsic size is zero.
Make sure to take intrinsic sizes into account when defining your view hierarchy. Some elements are bulky by nature (like the UIDatePicker, that presents a date picker), while others will not look good if you constraint them to a size smaller than their intrinsic size (i.e: the text of a label will be partially hidden).
If you specify the multiplier for a size constraint, you can get some pretty nice results, like making a view be twice as wide as another one, or make a view take 1/3 of its parent’s view space. This not only is the best way for big elements that are going to take whole parts of the screen in a responsive way, but also allows for some pretty cool user interface scenarios, and we will explore them in depth in further episodes of this tutorial.
What You See Is NOT What You Get
One important thing to remember when working with AutoLayout is that what you see on screen, and how the views will finally display on the device, is not always the same. Even though you have just defined a set of constraints to the position and size of an element, you can still move and resize that element visually on Interface Builder. When you do that, the current situation of the element changes, but the constraints you defined previously don’t. Thus, if you run the application, the view will not appear with the position and size that you see right now, but with the position and size dictated by its constraints.
An example is depicted in the figure at the left. Imagine that we have added a label in the view of our view controller, and we have pinned the view to the top left corner. Now imagine that we move the label to a more centered position. You will notice that two orange indicators appear with a value on each. They indicate that the current position of the label is not the position that it will have when running the app, because the constraints will be applied and the label will position accordingly. The same would happen if you changed the size of the view.
Therefore, be aware that the position of the views on screen might not correspond to their “real” position. If in doubt, you can always click on any view (the label, for example) and have a visual clue of its position:
- If the view appears defined by a set of blue guides, it’s not only completely defined by AutoLayout constraints, but it’s currently in the position and size that will be displayed when the application runs on a real device.
- If the view appears with orange guides (like in the previous pic), the view’s constraints have been correctly defined, but it’s not positioned and sized as it will appear on a final device. In this situation, you can click the “Update Frames” button we described previously, and the view will automatically refresh its position and size.
- If the view don’t show any guides or show red ones, the view’s constraints have not been defined, have conflicts or are incomplete, so the position and size of the view on a final device cannot be determined.
This last case is a completely undesired situation. One of our tasks when building the user interface of our application is making sure that all constraints are properly defined for EVERY view or subview in all of our storyboards and Nib files. We
Ambiguity and Conflicts
We showed in a video at the beginning of this tutorial what happens if you don’t define any constraint for a view. However, we might run into the same situation if we have defined some constraints for a view, but they are not complete or present a conflict. Let’s analyze both cases:
Conflicts occur when one or more views have constraints that cannot physically coexist together. This can happen at an individual level of as a result of the interaction of contradictory constraints from two views.
One example of the former will be the following: I define a constraint for my view that pins the view to the top, left, right and bottom of the screen, (covering the whole screen), and then I specify a height constraint of 20 for that view. As you might imagine, this will create a conflict, because all screens from iPhones and iPads have more than 20 pixels in height, so you cannot have a view from top to bottom of an iPhone and have it be 20 pixels in height. That’s a paradox. The way of solving these situations is removing the constraint that doesn’t make sense: do we really want our view to cover the whole screen, or do we want it to be 20px in height?
These kind of mistakes and contradictory constraints in the same object are more frequent that you might think. But they are also the easiest ones to spot and fix.
The presence of contradictory constraints between two or more views is usually harder to solve, and might indicate an incorrect definition of constraints in several objects. In this situation, we need to find the offender constraint, or spot the two or more constraints that are conflicting, and decide on the constraint that needs to be removed or modified. let’s see a concrete example:
Here, we have a button on top and a label at the bottom. Imagine that we define the following constraints for them:
- For our top button, we pin it to the top of the screen, left and right edges, and we pin it to the label in the bottom edge (so we affirm that the bottom of our button needs to be pinned to the top of the label). We also set a constant height of 368 px for the button.
- For the bottom label, we pin it to the top of the screen, left and right edges, and to the bottom of the screen. We also have the pin from the bottom of the button to the top of the label. We could read that constraint for our label as: “The top of our label should pin to the bottom of the button).
You will easily see how this creates a conflict: the label cannot be below the button and pinned to the top of the screen under this conditions. The picture shows the four conflicting constraints that we can modify. Let’s review the effects that removing them will have on how our controls display on screen:
- Constraint number 1 specifies that the button’s top should be pinned to the top of the screen. If we remove this constraint , the label will expand to the top of the screen, and the button will also move so that it’s bottom is now at the top of the screen, and it expands past the top edge, precisely taking 368px of space. We probably don’t want this, as it will hide most of the button.
- Constraint number 2 pins the top of the label to the top of the screen. As you might see, currently the label’s top is not there. If we remove this constraint, the configuration will stay as it is currently. This makes sense if we want our label to stay at the bottom of the button.
- Constraint number 3 specifies the height for our button. If we remove this constraint something funny happens. Without the height constraint, the button will expand all the way down, shrinking the label to its intrinsic size, but we’ll still have a conflict, as the button and the label still want to be pinned to the top of the screen while stacked one on top of the other, and that’s impossible.
- Constraint number 4 sets the button on top of the button. If we remove this constraint, then both the button and the label can be pinned to the top of the screen, so they will all move there, and one will “overlap” the other. The button will keep its height of 368px, and the label will occupy the whole screen (as it’s pinned to the bottom).
So, as you can see, the final resolution of a conflict vary wildly depending on the constraint you remove. Think about your user interface, your design, and where should your elements be displayed, and act accordingly. Usually, there’s only one satisfactory option. In the example depicted above, probably option number 2 is the most reasonable of the four, but depending on the user interface, option 4 or even 1 might be an option.
Ambiguity means that we have defined some rules for a view, but they are not enough to absolutely specify its position and size. The figure at the left shows an example: we added a label in the center of our view, and then added a constraint to center it horizontally. You might think that this is enough, but we didn’t specify the vertical position of the label. The red line indicates that the label could sit in any of the coordinates of the Y-axis while still being horizontally centered. We need to set the vertical position of the label. This is what I call “missing constraints ambiguity“, because it’s caused by one or more missing constraints.
You can have ambiguity also in the size of one or more elements. This usually happens when you define the constraints of two elements relative to each other, but the sizes of the elements are not specified. In these scenario, the two views might be in multiple different situations depending on their size. This is what I call “size ambiguity“. Let’s see an example, and how to solve it:
Solving size ambiguity
Imagine that we have the following situation: we have added two simple labels to our view controller’s view, position one on top and another on the bottom, and pinned both to their edges. The top label will be pinned to its superview on the top, left and right edges, and pinned to the other label in its bottom edge. On the other hand, the bottom label will be pinned to its superview in its bottom, left and right edges, and to the other label in its top edge. The figure shows this situation.
Notice how, even though we have clearly defined their edges correctly, we didn’t define their sizes. Thus, the top label might look like a title, taking only 20% of the screen, while the other view might take the rest, or they could, for example, have the same height, and its parent view would be divided equally between the two labels. This is a classic example of size ambiguity.
To solve size ambiguity, we can take two different approaches:
Add a new constraint
- We could set another constraint that will define the size of one of the views, and then the other will take the remaining space. We can, for instance, set the upper label’s height to 60. The lower will automatically take the rest (this is the “title”-like situation I described). We could also add a “Equal height” constraint between the two, like the picture below depicts (that’s the second situation I described).
Set the hugging priority or the content compression resistance
- We could also define what’s called the “Hugging Priority” and the “Content Compression Resistance” of at least one of the views. This values serve to untie these ambiguity scenarios. The first, “Hugging Priority”, refers to how keen is the view to shrink or reduce its size when other surrounding views would like to take its space. The default value for this property is 251 (yep, not 100, not 500, exactly 251). The “Content Compression Resistance” is exactly the opposite, it indicates how strongly will a view resist to yield ground to their neighbors. The default value of this property is 750 (again, odd value).
The tie situation in our scenario is due to both labels having the same values for “Hugging Priority” and “Content Compression Resistance”. If we want our top label to act as a “title” in the screen, we can easily untie the situation by increasing the vertical “Hugging Priority” of our top label by one, indicating that our label is more prone to shrink and leave some space for its bottom neighbor. We also decrease its vertical “Content Compression Resistance” by one, enough to indicate that our upper label will apply less resistance, effectively allowing the bottom label to grow as the picture above shows.
Notice how modifying the priority properties for the views respects their intrinsic size, so it only has effect when needed, and won’t completely hide or push a view outside. It’s a subtler, and more gentle way of polishing the constraints between multiple views. In our example, the top label will shrink just to its intrinsic size, and while to bottom label will occupy the rest of the screen, it will not hide or cover its upper friend.
Notice also how we didn’t need to modify the horizontal priority properties, because there’s no ambiguity there, both our labels are correctly pinned to the left and right edges of its superview.
Every AutoLayout constraint has a priority. By default, this priority is 1000. This number indicates, in case of conflict, which constraint will be finally applied, i.e: the one with higher priority.
Constraint priority can be used to do some AutoLayout-based animation. Imagine that you set two constraints for an element’s width: one sets the width constant to zero, and the other one to 100. We set the priority for the zero constraint to 1000, and the other to 999. As the zero constraint has a higher priority, the element is not shown. However, we add some logic so that, if we click on a button somewhere, the zero constraint’s priority will be decreased to 998. Then, the other constraint has now a higher priority, so a call to layoutIfNeeded() within the boundaries of a UIView.animateWithDuration(…) will generate a nice animation effect.
We’ll talk about AutoLayout animations and constraint priorities in depth in another episode of this tutorial series.
Where to go from here
In this first episode we explored how AutoLayout works and mastered defining and polishing constraints for our views. In the next episode of the series, we will go a step further and learn how to define responsive constraints. This will allow us to specify the relative sizes of the views in a responsive, device independent way.
But for now, experiment with AutoLayout! Create a new project, drop some views in your main UIViewController, and start defining constraints for them. Get some acquittance with the basic rules and how elements change when you add constraints between them. Have some fun and enjoy. If you find it hard to get to a desired result, don’t hesitate to ask in the comments.