Constraints Express Relationships Between Views
Constraints represent relationships between user interface elements such as “these views line up head to tail,” or “this button should move with this split view subview.”
A constraint is basically a statement of the form
y = m*x + b where
xare attributes of views.
An attribute is one of
trailingare the same as
rightin English, but the UI flips in a right-to-left environment like Hebrew or Arabic. When you create constraints using Interface Builder or the visual format strings (see “Create Constraints Programmatically Using an ASCII-Art Format String”),
trailingare the default values.
bare floating point values.
The following (presented as pseudocode) represents a concrete example of a constraint:
button1.right == button2.left - 12.0
Constraints are not directional, and there are no restrictions on cycles. Layout itself is neither top down nor bottom up. Instead, all constraints are considered at the same time, and elements laid out in such a way that best satisfies all of the constraints.
Constraints Can Be Inequalities
The relation in a constraint does not have to be
==; it can also be
>=. For example:
button1.width >= 100.0
Constraints Have a Priority Level
Constraints have a priority level. A priority level is a floating point value. Constraints with higher priority levels are satisfied before constraints with lower priority levels. The default priority level is
NSLayoutPriorityRequired, which means that the constraint must be satisfied exactly. The layout system gets as close as it can to satisfying an optional constraint, even if it cannot completely achieve it. If a constraint
a == b is optional, it means that
abs(a - b) should be minimized.
Priority levels allow you to express useful conditional behavior. For example, they are used to express that all controls should always be sized to fit their contents, unless something more important prevents it. For more detail on this example, see “A View Specifies its Intrinsic Content Size.”
Constraints May Cross View Hierarchies
Constraints can, with some restrictions, cross the view hierarchy. In Mail, for example, by default the Delete button in the toolbar lines up with the message table. In Desktop Preferences, the checkboxes at the bottom of the window align with the split view pane they operate on.
It is not legal for a constraint to cross through a view that is manually laid out, or through a view that has a bounds transform (such as a scroll view)—you can think of such views as barriers. There’s an inside world and an outside world, but the inside cannot be connected to the outside by constraints.
Create Constraints Programmatically Using an ASCII-Art Format String
You represent constraints using instances of
NSLayoutConstraint. To create constraints, you typically use
constraintsWithVisualFormat:options:metrics:views:. The first argument is an ASCII-art–inspired visual format string that provides a visual representation of the layout you want to describe. A view is in represented in square brackets, and a connection between views is represented using a hyphen (or two hyphens separated by a number to represent the number of points apart the views should be). Examples and a grammar for the visual format language are given in “Visual Format Language.”
For example, for this layout
you might represent the relationship between
A single hyphen denotes the standard Aqua space, so you could represent the relationship with just
You pass the visual format string as the first argument to
constraintsWithVisualFormat:options:metrics:views:. The views come from the
views dictionary—the keys are the names you use in the format string, the values are the corresponding views. As a convenience,
NSDictionaryOfVariableBindings creates a dictionary where the keys are the same as the corresponding value’s variable name. The code to create the constraints thus becomes:
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(button1, button2);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"[button1]-[button2]"
The visual format language prefers good visualization over completeness of expressibility; there are therefore constraints that cannot be expressed in visual format syntax, although most of the constraints that are useful in real user interfaces can be. One useful constraint that cannot be expressed is a fixed aspect ratio (for example,
imageView.width = 2 * imageView.height). To create such a constraint, you must use
constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:. As an example, you could create the
[button1]-[button2] constraint using the following code.
Constraints are Mostly Immutable
You cannot change most of the properties of a constraint after you create it, but you can still change the constant. There are two reasons for this:
It’s algorithmically important. It is much more efficient to change the constant than to remove a constraint and add a new one.
It leads to a natural way to implement operations such as dragging, which is exactly the case in which performance is most important.
To make a constraint active, you must add it to a view. The view that holds the constraint must be an ancestor of the views the constraint involves, often the closest common ancestor. (This is in the existing
NSView API sense of the word ancestor, where a view is an ancestor of itself.) The constraint is interpreted in the coordinate system of that view.
Suppose you install
[zoomableView1]-80-[zoomableView2] on the common ancestor of
The 80 is in the coordinate system of the container, which is what it looks like if you draw the constraint.
If the bounds transform of either of the zoomable views changes, the space between them remains fixed.
If the bounds transform in the container itself is changed, then the space scales too.
NSView provides a method—
addConstraint:—to add a constraint, and methods to remove or inspect existing constraints—
constraints—as well as other related methods.
NSView also provides a method,
fittingSize, which is similar to the
sizeToFit method of
NSControl but for arbitrary views rather than for controls.
fittingSize method returns the ideal size for the view considering only those constraints installed on the receiver’s subtree together with a preference for the view to be as small as possible. The fitting size is not the “best” size for a view in a general sense—in the constraint-based system, a view’s “best” size if you consider everything has to be the size it currently has!
Auto Layout Degrades Gracefully with Unsatisfiable Constraints
It is a programming error to configure constraints that cannot be satisfied. Faced with unsatisfiable constraints, however, the Auto Layout system attempts to degrade gracefully.
addConstraint:method (or the
NSLayoutConstraint) logs the mutually unsatisfiable constraints and throws an exception.
The exception is appropriate because this is a programmer error.
The system catches the exception immediately.
Although adding a constraint that cannot be satisfied is a programmer error, it’s one that can readily imagine occurring on a user’s computer, and one from which the system can recover more gracefully than crashing the program. The exception is thrown so that you notice the problem, and caught because it’s easier to debug the constraint system if the system does still function.
To allow layout to proceed, the system selects a constraint from among the mutually unsatisfiable set and lowers its priority from “required” to an internal value that is not required, but is higher priority than anything you can externally specify. The effect is that as things change going forward, this incorrectly broken constraint is the first that the system attempts to satisfy. (Note: every constraint in the set will be required to begin with, because otherwise it wouldn’t be causing a problem.)
2010-08-30 03:48:18.589 ak_runner[22206:110b] Unable to simultaneously satisfy constraints:
"<NSLayoutConstraint: 0x40082d820 H:[NSButton:0x4004ea720'OK']-(20)-| (Names: '|':NSView:0x4004ea9a0 ) >",
"<NSLayoutConstraint: 0x400821b40 H:[NSButton:0x4004ea720'OK']-(29)-| (Names: '|':NSView:0x4004ea9a0 ) >"
Will attempt to recover by breaking constraint
<NSLayoutConstraint: 0x40082d820 H:[NSButton:0x4004ea720'OK']-(20)-| (Names: '|':NSView:0x4004ea9a0 ) >
© 2012 Apple Inc. All Rights Reserved. (Last updated: 2012-09-19)