Working with Auto Layout Programmatically

Even though Interface Builder provides a convenient visual interface for working with Auto Layout, you can also create, add, remove, and adjust constraints through code. If you add or remove views at runtime, for example, you’ll need to add constraints programmatically to ensure that your interface responds correctly to changes in size or orientation.

Creating Constraints Programmatically

You represent constraints using instances of NSLayoutConstraint. To create constraints, you typically use constraintsWithVisualFormat:options:metrics:views:.

The first argument to this method, a visual format string, provides a visual representation of the layout you want to describe. This visual format language is designed to be as readable as possible; a view is 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). For more examples and to learn the grammar for the visual format language, see “Visual Format Language.”

As an example, you might represent the constraint between two buttons:

../Art/twoButtons.png

using the following visual format string:

[button1]-12-[button2]

A single hyphen denotes the standard Aqua space, so you can also represent the relationship like this:

[button1]-[button2]

The names of the views come from the views dictionary—the keys are the names you use in the format string, and the values are the corresponding view objects. 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 becomes:

NSDictionary *viewsDictionary =
                NSDictionaryOfVariableBindings(self.button1, self.button2);
NSArray *constraints =
        [NSLayoutConstraint constraintsWithVisualFormat:@"[button1]-[button2]"
                            options:0 metrics:nil views:viewsDictionary];

The visual format language prefers good visualization over completeness of expressibility. Although most of the constraints that are useful in real user interfaces can be expressed using the language, some cannot. 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 can use constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:.

As an example, you could also use this method to create the earlier [button1]-[button2] constraint:

[NSLayoutConstraint constraintWithItem:self.button1 attribute:NSLayoutAttributeRight
                      relatedBy:NSLayoutRelationEqual toItem:self.button2
                      attribute:NSLayoutAttributeLeft multiplier:1.0 constant:-12.0];

Installing Constraints

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, and should usually be 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 zoomableView1 and zoomableView2.

../Art/helloHi-1.png

The value 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.

../Art/helloHi-2.png

If the bounds transform in the container itself is changed, the space scales, too.

../Art/helloHi-3.png

NSView provides a method—addConstraint:—to add a constraint, and methods to remove or inspect existing constraints—removeConstraint: and 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.

The 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) is by definition its current size.