May 12th, 2016
In a world with an increasing differentiation of screen sizes, it’s important to think about adaptivity right from the design process. However, not all designers keep this in mind. If this is the case, design choices for different screen sizes are not visible to the designer and might not be made. In most cases, it’s up to the interpretation of the developer to choose a correct solution, although these decisions should’ve been made by the designers themselves.
This blog is meant for designers to solve the problem above by describing a layout in layout constraints, instead of just using absolute pixels.
When describing a layout using natural language instead of pixel information, you will almost always get an adaptive layout that’s usable for different screen sizes. For example, the Apple Weather app. The interface of this app can easily be described by the following rules:
- Center the temperature label horizontally in the window;
- Pin the temperature label’s top, to the top of the window;
- Pin the temperature label’s bottom, to the center-Y of the window;
- Pin the weather forecast’s left, right and bottom to the left, right and bottom of the window;
- Pin the weather forecast’s top to the center-Y of the window.
Each rule in the list above represents a layout constraint.
In the example of the Weather app, layout constraints let you center and pin elements, but there are more things. To explain what a constraint should describe, let’s take the following constraint:
Pin the temperature label’s bottom, to the center-Y of the window.
And let’s decompose it into functional blocks:
Pin the temperature label’s bottom, to the center-Y of the window.
As you can see, a constraint should contain a subject, the attribute of the subject, a relative object and the attribute of the relative object. There’s one exception to this rule: describing the width or height of a subject. As this kind of constraint does not refer to another object, but just to itself. In addition to relative positioning, an iOS layout constraint can also describe a relationship, offset constant or multiplication:
The temperature label’s center-Y, should be greater than the window’s center-Y times 0.5, plus 20 points.
As this constraint looks a bit complex, this syntax can describe all possible layout properties. A simple centering constraint for example would look like this:
The temperature label’s center-X, should be equal to the window’s center-X times 1, plus 0 points.
A constraint that describes a width or height would only have a subject, attribute, multiplication and a constant.
A constraint can define a relative position from the following attributes of a subject:
- Leading (the side where a line starts, could be reversed in some countries)
- Trailing (the side where a line ends, could be reversed in some countries)
- Baseline (the text’s baseline of the last line)
- First baseline (the text’s baseline of the first line)
On top of these positions, a constraint can also be described relative to a predefined margin from the object. This is possible for left, right, top, bottom, leading, trailing, center-X and center-Y.
It’s possible to define multiple layout constraints for a single view. This means that it’s possible to define constraints that contradict each other. This is called ambiguity.
To explain ambiguity, let’s look at the following constraints:
- The label’s center-X, should be equal to the window’s center-X;
- The label’s leading, should be greater than the window’s leading, plus 15 points;
- The label’s trailing, should be smaller than the window’s trailing, minus 50 points.
By default, each element gets the dimensions of its content, unless there are constraints that define other dimensions. This content size is called the intrinsic content size. This won’t cause any problem as the label is small. However, when the content starts to grow (which might be the case when the app is being localized in a language that uses longer or more words), problems arise. The label begins to grow beyond the 50 points at its trailing. It won’t be possible to either center the label horizontally, or have a trailing space of at least 50 points. The iOS layout system will give a warning and will remove a random constraint from the conflicting constraint. This might cause unexpected layout behaviour. To solve the problem in this case, we can lower the centering constraint priority, so that the label shifts to the left whenever the label becomes bigger.
Content Hugging Priority
Let’s say we have a text compose view with a send button next to a text field. The horizontal constraints would look like this:
- The textfield’s leading, should be equal to the window’s leading margin;
- The textfield’s trailing, should be equal to the button’s leading, with an offset of 8 points;
- The button’s trailing, should be equal to the window’s trailing margin.
There’s a problem with these constraints. How does the layout system know how big the textfield or the button is? As mentioned, the layout system uses the intrinsic content size of elements, but we have two elements with an intrinsic content size next to each other. In this situation, there’s an ambiguity and the system needs to choose between blowing up either the textfield’s width, or the button’s width. To fix this, we can define a content hugging priority on all views. A content hugging priority describes the need for a view to “hug” its edges while blowing up its window.
A content hugging priority describes the need of the view to “hug” its edges while blowing up its window.
In this case, we would give the button a higher content hugging priority than the textfield.
Content Compression Resistance
Let’s say we still have a text compose view as shown in the last paragraph’s example. The view in that example was bigger than both intrinsic content sizes. But what if we have such a small view that the subviews would be compressed instead of blown up? This is what the content compression resistance is about. The content compression resistance describes the need for a view to keep to its intrinsic content size while compressing it.
The content compression resistance describes the need for a view to keep to its intrinsic content size while compressing its window.
Layout constraints are really powerful in describing the layout, so that it can be laid out for all kinds of screen sizes. It works very well for interfaces that can be blown up or compressed. However, sometimes we have a different kind of design for different kind of devices. The Apple Settings app for example has a sidebar at the left on the iPad, where the iPhone app does not have this. This is possible by conditionally defining layout constraints, based on a collection of device properties. Such a collection is called a “Trait Collection”.
A size class describes whether a screen’s axis is either “Compact” or “Regular”. A few use case examples:
- The iOS settings app’s left sidebar is based on the horizontal size class. If it’s regular, the sidebar is being displayed.
- The visibility of the status bar. If the vertical size class is compact, the statusbar is hidden.
- The navigation bar height. If the vertical size class is compact, the height will be a bit smaller.
Layout constraints that are defined in Xcode’s Interface Builder can be conditionally set based on a screen’s horizontal or vertical size classes. When the size class changes (for example by rotating, or changing the app window size on the iPad), iOS will automatically change the current used layout constraints. This is an overview of the used size classes on different iOS devices:
In code, layout constraints can be based on more variables than just the horizontal and vertical size class:
- Display scale
- User interface idiom (iPhone, iPad, TV or CarPlay)
- Force Touch capability