With the release of Xcode 5 and iOS 7 the need to learn, understand, and use Auto Layout become critical. This post reviews the changes to Auto Layout in Interface Builder (IB) in Xcode 5. This article is based on my experience using Auto Layout on a very large iPad app built using Xcode 4 and examining how I would build parts of that app if I had Xcode 5 from the start.
What is Auto Layout
Auto Layout is a constraint-based layout system available in iOS 6.0 and above. In Auto Layout you describe the relationship between views and iOS calculates the location and size of the views at runtime. This approach allows the layout to react to changes in screen and content size without requiring your code to change. It is important to note that Auto Layout is the only way that views are laid out in iOS, and while you may specify the layout using springs and struts in IB, but iOS always translates them to constraints at runtime.
In iOS 6, Auto Layout was available and recommended, but not required. In iOS 7, Auto Layout has become more important, to the point of practically being required, because it facilitates the ability, provided by Dynamic Type, for users to select the font size to use in standard button, label, and text field controls, and then have the rest of the screen respond correctly to the change in font size..
A welcome change to Xcode 5 is greatly enhanced control over Auto Layout in Interface Builder. In Xcode 4, the creation of a slightly complicated view was close to impossible because Xcode did too much to ‘help' the developer, but didn't give the developer any way of correcting it's ‘help'. Xcode 5 still has the ability to help the developer, but it only does so at the request of the developer. Yay! We humans are in control again.
Because of Xcode 4's over-helping in IB, many developers frequently reverted two the two other ways to implement Auto Layout based views: Visual Format Language (VFL) and manually instantiating NSLayoutConstraint objects. Thankfully, because of the improvements in IB you'll probably not be using these methods as much in the future. I've found that views that were impossible to build in Xcode 4 IB can now be created quickly and easily in Xcode 5.
Auto Layout uses NSLayoutConstraint objects to describe the relationship between views. A single NSLayoutConstraint object can only describe one relationship. To accurately specify the positioning of a view the constraints assigned to it must resolve to an X & Y position and a width & height. iOS evaluates all of the constraints in a view hierarchy, finds a valid solution where all of the constraints can be satisfied, and positions the views based on that solution. It will use priorities assigned to the views to arbitrate conflicts in the resolution of the constraints. If you don't specify a priority then iOS assigns the highest priority. If any one of positional or dimensional constraints are lacking on a view iOS will consider it an ambiguous layout and the position and size of the view will be indeterminate. If a view has conflicting relationship definitions, for example two constraints, with the same priority, that define the width, then iOS will log an error describing the conflict and discard one or more constraint that is causing the conflict. In some situations you may have multiple constraints that evaluate to identical answers, but iOS may still consider those to be conflicting.
Thinking in Auto Layout
One of the problems that have beset developers adopting Auto Layout is the need to change how you think about the position of our views. Before Auto Layout you thought about frames and bounds; size and position was everything. However, you must now think in terms of relationships rather than sizes.
I've seen two ways to think about relationships in Auto Layout: outside-in and inside-out. The outside-in way of thinking starts from the superview and applies relationships down the view hierarchy until you reach the lowest level UILabels, UIButtons, and other UIKit views. I've found that using this approach alone can easily lead to setting sizes on those lower level components, which violates the principle of thinking relationally.
The inside-out method starts by letting the lower level components specify their desired sizes via their intrinsic content size. Then their containing views are allowed to grow to contain their size or to limit their size based on external limitations (like the screen size).
The truth is that both methods end up working together to define the layout. The resolution of a constraint equation works both from the inside-out and outside-in. Constraints on subviews can limit the size of the superview and constraints on a superview may limit the size of its subviews.
There's now a new way of thinking about laying out views in IB. In Xcode 4, as you placed or moved views in a xib, IB would continuously recalculate the constraints required to make the view as you had laid it out. This had the unfortunate side effect of IB constantly fighting what you wanted to do, and if you had manually created constraints it could delete them willy-nilly. It also seemed to add constraints that were not necessary. Thankfully, IB doesn't do that anymore.
Now, as you position views in IB it makes no assumptions about what constraints you want. You can get all of your views exactly where you want them (it still provides helpful guides to let you line things up). Once you've placed a view you can then ask IB to create the constraints to create the position you want, or you can specify those constraints yourself. If you create a constraint it will not remove it unless you ask it to.
I've found the best approach to be:
- Place the views where you want them
- Manually add constraints to each view to give it the behavior you want
- Let IB add missing constraints
- Test in preview mode
Quick Tour of XCode 5 Interface Builder
When you open a xib file in XCode file you get a familiar looking screen. The most significant change is in the lower-right of the layout area. Apple has completely reworked the controls, shown in the Figure 1.
In the example for this blog, we'll be defining constraints to layout a 10-button numeric keypad that resizes as it's containing view resizes. In Xcode 4, this particular view was impossible to build using IB.
The Add New Alignment Constraint button (the left most button in Figure 2) in the controls brings up a menu that allows you to constrain views in relation to other views. These controls now allow you to specify a constant value to go with the constraint.
Each of the items in the popover menu allows you to define alignment constraints between two or more views. For example, if you have two views selected and choose the Leading Edges checkbox, then IB will add a constraint to align the views' leading edges. If you specify a number in the constant box to the right, it will adjust the constraint by the value of that constant.
When you add constraints to views using this method, you have the option of updating frames. By default, when you add a constraint this way, IB will not reposition the affected views to reflect the behavior of the constraints. The Update Frames control has 3 options:
- None: No views are repositioned
- Items of new constraints: Only those views that have constraints added will be repositioned.
- All Frames in Container: All views in the container are repositioned, whether views were added or not.
Be aware that if you use items 2 and 3 while the constraints are still ambiguous your views may appear in odd places or become invisible. I've found it best to wait until I think that I've fully specified the constraints before instructing IB to reposition the frames.
Many of these same relationships can be defined by control-clicking on a view in your layout and dragging the cursor to the related view. This works with any view in the hierarchy including the containing superview. In Figure 3, I'm connecting the buttons for 5 and 6.
The next control, the Add New Constraints control, in IB, show in Figure 4, adds new spacing and sizing constraints to your views.
The top portion allows you to specify spacing between views. You can specify default values or specific values in each of the combo-boxes relating to a side of the view. If you've only selected one view, the spacing applies to relation between the selected view and its superview. The spacing combo boxes will also present values used in other constraints within this same view.
Laying out a complex view
- Think relationally; don't think about frames and sizes.
- Don't think linearly; Auto Layout doesn't evaluate constraints in order. They are all considered equally, and conflicts are resolved via priority.
- Don't over-specify constraints. Just say as little as possible to communicate the layout and no more.
- Avoid letting IB define constraints for you. Unless your view is very simple, IB will probably get it wrong.