Getting Started with Auto-Layout in iOS 6

With the release of iOS 6 Apple has added significant enhancements to the way developers create and change layouts within their applications. One of the huge enhancements in iOS 6 is the inclusion of the Auto Layout libraries. Mac OS developers will be familiar with Auto Layout as it made its first appearance in OS X Lion where it provided a productivity boost to developers dealing with multiple resolutions and resizing windows. With the release of the iPhone 5, and its new screen form factor, developers will soon be looking to Auto Layout out of necessity.

Auto Layout provides developers with some serious productivity enhancements. The new library improves the division of responsibility between the controllers and their sub views by removing much of the glue code that would usually handle the resizing of elements, such as labels and text views, as they adapt to deal with varying content. With Auto Layout, elements can be pinned to the edge of the view or the edges of other subviews, thus automatically handling a lot of the manipulation that takes place during orientation changes. Lastly Auto Layout, coupled with some other enhancements, such as the addition of the NSDoubleLocalizedString variable, makes localizing and testing your application much easier.

Enabling Auto Layout

Adding Auto Layout support to your iOS 6 application is easy. If you are starting a project from scratch then Auto Layout will be on by default. If you are updating an existing application then you can find the "Use Autolayout" check box in the Interface Builder Document menu.

Interface Builder - Auto Layout

Now, with Auto Layout enabled, you can begin adding and adjusting your
Constraints. By default Interface Builder will attempt a best guess at a set of minimum, non-conflicting constraints when adding constraints to your current view. These constraints are often very specific and need to be changed or removed to achieve the desired results
Now, with Auto Layout enabled, you can begin adding and adjusting yourConstraints. By default Interface Builder will attempt a best guess at a set of minimum, non-conflicting constraints when adding constraints to your current view. These constraints are often very specific and need to be changed or removed to achieve the desired results.

Viewing and Adding Constraints in Interface Builder

There are several menus in Interface Builder which aid in the adding and adjustment of Constraints on views. In the bottom right hand corner of Interface Builder, next to the Zoom Out button, there is a menu that allows you to see the Constraints that can be added to a selected view or views.

Interface Builder - Constraints

A basic example of a time when Constraints are useful is when the text of a UILabel can grow or shrink and the siblings of the label need to adjust to accommodate the growth. When you first add your interface elements to your view in Interface Builder it will usually pick Constraints based on the bounds of your superview. The result of this is that if the label text grows, as is often the case when localizing to another language, it will clip with the surrounding interface elements.

Notice in the image below that the horizontal constraint for the text field is the edge of the superview and not the side of the UILabel. When the text of the label changes it will conflict with the text field.

Initial IB Constraint

After the Text Grows

Auto Layout makes issues like this very easy to resolve. The first thing we need to do is select the two related interface elements. In the Constraints menu, verify that Siblings and Ancestors are both selected. With both the label and the text field selected, open the Constraints menu and select the "Width" constraint. Next we need to remove the existing constraint tying the UITextField to the left of our superview. This is done by simply selecting the Constraint and deleting it. If we run our test again the UITextField will now shift over to make room for our expanding label.

Siblings

The final constraints

Results of the test

Not only can you control spacing with the constraints, but you can control width as well. In this example what if we wanted the UITextField to take up all of the space not consumed by the UILabel? The first thing we need to do is open up Interface Builder and drag the width of the UITextField so that it takes up the maximum desired space. Next we need to adjust the "Content Compression Resistance Priority" of our text field. This priority can be set on the View menu in interface builder. When it comes to priority settings the view with the highest value always wins. In this example the horizontal Compression Resistance Priority simply needs to be lower than the UILabel. With that set the text field will now take up the desired space while still resizing when the label text changes.

Priority Settings in the View menu

The Result

The Result Long

Adding Constraints in Code

Interface Builder does a great job making visualization and adjustment of autolayout constraints easy but oftentimes we need to add and remove interface elements dynamically. Fortunately adding and removing constraints directly in code follows the same rules as Interface Builder and thus any constraint that you can add visually can also be done in code.

When implementing constraints in code you will make use of an ASCII style visual representation of the user interface elements. This can be quite complex but there are a few basic syntax rules to learn that can get you up an running. Below is an example of the constraints we might add to mimic the constraints Interface Builder added for our UILabel and UITextField.

<span class="co2">//add constraint to keep textfield to the right of the label</span>
<span class="br0">[</span>self.view addConstraints<span class="sy0">:</span><span class="br0">[</span>NSLayoutConstraint constraintsWithVisualFormat<span class="sy0">:</span>
 <span class="co3">@</span><span class="st0">"H:|-[_sizeLabel]-[_demoTextField(==100)]"</span>
 options<span class="sy0">:</span><span class="nu0">0</span>
 metrics<span class="sy0">:</span><span class="kw2">nil</span>
 views<span class="sy0">:</span>NSDictionaryOfVariableBindings<span class="br0">(</span>_sizeLabel,_demoTextField<span class="br0">)</span><span class="br0">]</span><span class="br0">]</span>;
 
<span class="co2">//Example of a constraint to position the Y axis of the text field.</span>
<span class="br0">[</span>self.view addConstraints<span class="sy0">:</span><span class="br0">[</span>NSLayoutConstraint constraintsWithVisualFormat<span class="sy0">:</span>
 <span class="co3">@</span><span class="st0">"V:|-(170)-[_demoTextField]"</span>
 options<span class="sy0">:</span><span class="nu0">0</span>
 metrics<span class="sy0">:</span><span class="kw2">nil</span>
 views<span class="sy0">:</span>NSDictionaryOfVariableBindings<span class="br0">(</span>_demoTextField<span class="br0">)</span><span class="br0">]</span><span class="br0">]</span>;

The NSLayoutConstraint take an argument called constraintsWithVisualFormat. In this argument you will pass in the ASCII representation of your desired constraint. In the first constraint, @"H:|-[_sizeLabel]-[_demoTextField]", the "H" specified the horizontal direction of the constraint. Likewise if we wanted a vertical constraint we would use "V". The next token of interest is the vertical bar "|". The bar represents the boundary of our superview. The dash between the bar and our label represents the default space iOS puts between elements. If we wanted to add extra space, as was done to vertically position the text field in the second constraint, we can do so by placing a value in parenthesis followed by another dash.

Lastly, we have our two views within square brackets. Notice that we are declaring the size constraints of the text field inside the brackets of the view. In this case we are locking our view to 100. The ASCII representation of the views is matched up with the views themselves by using the NSDictionaryOfVariableBindings that we pass in the views argument.

Auto Layout is great for positioning existing elements and adjusting elements when new views are added. So what happens when views are removed? When a view is removed from the superview all constraints pertaining to that view are also removed.

A full breakdown of the ASCII syntax used in declaring constraints can be found in the Cocoa Auto Layout release notes.

Troubleshooting Conflicts

One of the first things you are likely to encounter when adding constraints in code is the error "Unable to simultaneously satisfy constraints". During the event of a conflict iOS will break constraints in an attempt to resolve the conflict. This prevents outright application failure but does not generally provide the desired result.

In the event of constraint violations the log message in the console will list potential offending constraints. Oftentimes, these constraints will be things that you added yourself and thus they can be fixed by changing your code directly. Sometimes, however, there will be several constraints that were not added within one of your classes.

The most common cause of the simultaneous constraints violation is forgetting to turn of the translateAutoResizingMaskIntoConstraints property. For compatibility purposes views will generate constraints in a way that mimic the springs and struts style of view placement. When you are adding your own constraints you will usually violate one or many of these auto-generated values. Fortunately turning off these constraints can be done with a single line of code.

//turn off constraints to avoid conflicts
[_demoTextField setTranslatesAutoresizingMaskIntoConstraints:NO];

Dynamic Views

Often developers will create a large part of their views in Interface Builder but still need to load some views dynamically at runtime. When this is the case you will have to deal with, not only adding constraints of your own to the new view but also removing constraints from existing views. You can interface directly with an NSConstraint by defining it as an IBOutlet in your view controller. Interface Builder makes this especially easy by allowing you to ctrl+click and drag the desired constraint directly into your class.

Once you have added a constraint as an IBOutlet it is a simple matter to add and remove it as needed when adding view dynamically. In this example we add and remove a header image. To add the image the top constraint for the label must first be removed. Once it is removed the ImageView can be loaded without conflict. Do not forget to add the constraint back when removing dynamically loaded view!

<span class="sy0">-</span><span class="br0">(</span>IBAction<span class="br0">)</span>showHideImage<span class="sy0">:</span><span class="br0">(</span><span class="kw4">id</span><span class="br0">)</span>sender<span class="br0">{</span>
 <span class="kw1">if</span><span class="br0">(</span><span class="br0">[</span>captechLogo isDescendantOfView<span class="sy0">:</span>self.view<span class="br0">]</span><span class="br0">)</span><span class="br0">{</span>
 <span class="br0">[</span>captechLogo removeFromSuperview<span class="br0">]</span>;
 <span class="br0">[</span>self.view addConstraint<span class="sy0">:</span>_titleTopConstraint<span class="br0">]</span>;
 
 <span class="br0">}</span> <span class="kw1">else</span><span class="br0">{</span>
 <span class="br0">[</span>self.view addSubview<span class="sy0">:</span>captechLogo<span class="br0">]</span>;
 <span class="br0">[</span>self.view removeConstraint<span class="sy0">:</span>_titleTopConstraint<span class="br0">]</span>;
 <span class="co2">//Example of a constraint to position the Y axis of the text field.</span>
 <span class="br0">[</span>self.view addConstraints<span class="sy0">:</span><span class="br0">[</span>NSLayoutConstraint constraintsWithVisualFormat<span class="sy0">:</span><span class="co3">@</span><span class="st0">"V:|-[captechLogo]-[_titleLabel]"</span>
 options<span class="sy0">:</span><span class="nu0">0</span>
 metrics<span class="sy0">:</span><span class="kw2">nil</span>
 views<span class="sy0">:</span>NSDictionaryOfVariableBindings<span class="br0">(</span>captechLogo, _titleLabel<span class="br0">)</span><span class="br0">]</span><span class="br0">]</span>;
 
 
 ...
 <span class="br0">}</span>
<span class="br0">}</span>

Centering our logo over our new title is easy. We simply set the NSLayoutAttributeCenterX constraint on the UIImageView with a relationship to our title attributes center.

<span class="co2">//center the image</span>
<span class="br0">[</span>self.view addConstraint<span class="sy0">:</span><span class="br0">[</span>NSLayoutConstraint constraintWithItem<span class="sy0">:</span>captechLogo
 attribute<span class="sy0">:</span>NSLayoutAttributeCenterX
 relatedBy<span class="sy0">:</span>NSLayoutRelationEqual
 toItem<span class="sy0">:</span>_titleLabel
 attribute<span class="sy0">:</span>NSLayoutAttributeCenterX
 multiplier<span class="sy0">:</span><span class="nu0">1.0</span>
 constant<span class="sy0">:</span><span class="nu0">0</span><span class="br0">]</span><span class="br0">]</span>;

Easy Localization

One of the biggest and most obvious benefits of Auto Layout coming to iOS is that it makes implementing and testing the localization of applications very easy. We have already demonstrated how we can control the behavior of subview size and position using constraints. A primary concern when localizing an application is making sure that all of your user interface elements adapt correctly when using languages that are longer or shorter than the one in which the application was designed.

One of the new helpful features in Xcode that will allow developers to quickly and easily test the adaptability of their user interfaces is the ability to add arguments to a scheme that are passed on launch. With iOS 6 if you want to test how your views will respond to longer versions of localized string then you can add the parameter NSDoubleLocalizedStrings to the arguments list. The result of adding this argument is that all localized strings in the application will double their length. Because we are using Auto Layout everything behaves as expected without the need for any additional code!


Conclusion

With the addition of Auto Layout to the iOS toolbox Apple has taken a huge step forward in view management. Manually creating and manipulating frames can be tedious and spring and struts can become unmanageable on more complex views. While it may take some effort to convert existing application to use the constraints, developers will find that they have an easier time accounting for variable content sizes and dynamically loaded view components.