What is Auto Layout?

Auto Layout changes the way that you create your iOS and OS X user interfaces in simple, but powerful ways. Rather than explicitly setting the origin and size of UI elements, you declare constraints between views that describe how UI elements are displayed. Constraints represent the relationship between two user interface elements.

  • This view stays a fixed distance from the top.
  • This view is the same size as another view.
  • This view is centered in the superview.

You create basic user interfaces in Interface Builder without using auto layout by using the familiar “springs and struts” method.

  • UI layout is accomplished by specifying the location and size of each element and you specify the resizing behavior of a view relative to its superview.
  • The margins for a given view could be a fixed distance to the superview or flexible.
  • Width and height of the view could be fixed or scale with the changes to the superview.

Interface Builder's springs and struts user interface

You can turn auto layout off or on in the Interface Builder file inspector for the entire Storyboard or xib file.

Interface Builder's user interface for turning auto layout off and on

Note: you cannot turn auto layout on and off for a single view; the “unit of currency” (as Mark Dalrymple likes to say. Hi Mark!) is the individual xib or storyboard file.

It has always been fairly easy to use springs and struts to create basic UI that, for example, causes a view to hug the edges of its superview. However, there are many situations that springs and struts don’t handle well. For example, two UI elements that look great in portrait orientation may autosize for landscape orientation in a way that causes them to overlap.

Constraints-based layout in IB

When you turn auto layout on in Interface Builder, the springs and struts UI is replaced by UI that allows you to change the properties of auto layout constraints. With auto layout turned on, you will see UI like this appear.

New UI In Interface Builder for Auto Layout replacing S&S

Those thin blue I-beam shaped lines that Interface Builder added represent auto layout constraints. In this case, they constrain the button’s distance relative to the right edge of its superview.

In this tutorial you will use the new auto layout features in Interface Builder to layout a basic view.

Autolayout Project Options

Create a new Xcode project using the single view application template. Set Devices to iPhone, Use Storyboards to On, and Use Automatic Reference Counting to On.

Set Background to Light Gray

Open the MainStoryboard.storyboard file and change the background color of the view to light gray.

Button with HIG Spacing

Add a Round Rect Button to the view horizontally centered near the top of the view. Notice the blue guide bar shown between the button and the top of the view? This is a vertical space constraint that describes the buttons position relative to the top of the superview. As you move the button near the top of the view you will notice that it snaps into position consistent with Apple’s iOS Human Interface Guidelines. If you select the constraint you can look at the attributes inspector to see the attributes set on the constraint.

Button with non-HIG Spacing

If you move the button further away from the top you will notice that the Standard checkbox is no longer checked and the Constant displays the distance (in points) the button is from the superview. If you check the Standard checkbox you will see the button snap back into the HIG suggested standard spacing from the superview.

Two Buttons with HIG Spacing

Add another button adjacent to the existing button. You will see that Interface Builder has created a spacing constraint between the buttons.

As you move the buttons around the view you will notice that the constraints automatically rebuilding based on their placement.

Up until now all of the constraints have been automatically created by the system, but you are free to create your own custom constraints. Position buttons so they are diagonal from each other. Now use the editor->align menu or the convenient buttons along the bottom of the Interface Builder view as in the screen shot to horizontally align the two buttons to each other by selecting Horizontal Centers. Notice that a new Center X Alignment constraint has been created. You will also notice that when you create your own constraints they are given blue icons, while the system created constraints are given purple icons. While a given element is selected you can use the Size Inspector to view a list of all constraints that describe its layout.

Constraints that cross the view hierarchy

You can get pretty far by setting up layout constraints in Interface Builder, but you might have a need to add constraints programatically. Constraints can be created in code like so:

[NSLayoutConstraint constraintWithItem: firstButton
            attribute: NSLayoutAttributeLeft
            relatedBy: NSLayoutRelationshipEquals
            toItem: secondButton
            attribute: NSLayoutAttributeRight
            multiplier: 1.0
            constant: 12];

A constraint that is created in your xibs and storyboards can be an IBOutlet. To illustrate this point you will need to make some changes to the user interface.

View Crossing Layout Constraint Setup

  1. Add a UIView to the interface that takes up about half of the top of the UI. This view will serve as a container view for one of our buttons.
  2. Move one of the UIButton items into the new container view centered near the bottom and change the label to Top Button.
  3. Add a UILabel and change the text to read Middle Button and position it below the container view.
  4. Change the text on the other button to read Middle Button and position it to the right of the Middle Button label.
  5. Add a new UIButton to the bottom of the interface labeled Make Long Text and stretch it to fill the HIG width guidelines.

You might want to align the center of the Top Button with the Middle Button. The way that the view hierarchy is currently setup you can’t create the centering layout constraint across the views in Interface Builder. If you select one of the buttons hosted directly by the grey view controller’s view, you will be unable to select and apply a constraint to the button that is contained in the white subview. Interface Builder will instead select the white view. Fortunately you can do this in code.

Here is a diagram of what you are about to do. First you are going to get a reference to the auto layout constraint that is keeping the top button centered in its superview.

Auto layout diagram before

Then you will remove that constraint and add a new one to center the top button over the middle button’s position.

Auto layout diagram after

Add the following IBOutlet properties to the class extension of ENHViewController.m and wire them up to the corresponding elements in Interface Builder.

@interface ENHViewController ()

@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *topButtonCenterXConstraint;
@property (weak, nonatomic) IBOutlet UIButton *topButton;
@property (weak, nonatomic) IBOutlet UIButton *middleButton;
@property (weak, nonatomic) IBOutlet UIButton *makeTextLongButton;

@end

Replace the boilerplate implementation of viewDidLoad implementation with the following:

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.topButton removeConstraint:self.topButtonCenterXConstraint];
    NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self.topButton
                                                                  attribute:NSLayoutAttributeCenterX
                                                                  relatedBy:NSLayoutRelationEqual
                                                                     toItem:self.middleButton
                                                                  attribute:NSLayoutAttributeCenterX
                                                                 multiplier:1.0f
                                                                   constant:1.0f];
    [self.topButton addConstraint:constraint];
}

In the method above you are attempting to remove the existing centering constraint that the system added to keep the top button centered in the container view. Next you create a new layout constraint that binds the horizontal centers of the top and middle buttons. Finally the constraint is added to the top button.

Build and run the app. It will crash with the following error message.

Constraint_Exception

The system has helpfully hinted that you have installed a constraint on the wrong view.

In the implementation of viewDidLoad there is a common, but mistaken assumption that the constraints are added and removed from the button itself. In fact, the topButtonCenterXConstraint is added to the button’s superview. Open MainStoryboard.storyboard and select the vertical constraint to which you have connected the topButtonCenterXConstraint IBOutlet. Note that the constraint was actually added to the button’s superview.

Interface Builder's centering constraint on the top button is applied to its superview, not itself

Why is this? When creating a constraint between two views, the constraint must be applied to the closest common ancestor of the two constrained views. In the case of the topButtonCenterXConstraint you wish to remove, that view is the white host view. Since it is attached to the host view, the constraint should have been removed from the tops button’s superview instead of the button itself. The new constraint should be added to a common ancestor of the buttons it is trying to constrain, the superview of the containerView, which is the grey view.

Replace the implementation of viewDidLoad in ENHViewController.m with the following:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    [self.containerView removeConstraint:self.topButtonCenterXConstraint];
    NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self.topButton
                                                                  attribute:NSLayoutAttributeCenterX
                                                                  relatedBy:NSLayoutRelationEqual
                                                                     toItem:self.middleButton
                                                                  attribute:NSLayoutAttributeCenterX
                                                                 multiplier:1.0f
                                                                   constant:1.0f];
    [self.view addConstraint:constraint];
}

Build and run the application. You should now see the top button centered above the middle button.

Top button centered above the middle button

Priorities

Constraints also have relative priorities associated with them. This allows constraints to be flexible, so they can adapt to the views content. If it’s more important to you for a certain view to maintain a minimum size than for another, you can set this relative priority in constraints. To see this in practice add an IBOutlet for the middle label to the class extension:

@property (weak, nonatomic) IBOutlet UILabel *middleLabel;

Now wire up the following IBAction to the button labeled Make Text Long:

- (IBAction)makeTextLongButtonTapped:(id)sender
{
    [self.middleLabel setText:@"Hello Long World:"];
}

Build and run the application. Tap the Make Text Long button and you will see the text label change and become truncated.

Middle Label Truncated

If you rotate the simulator 90 degrees to landscape mode you will see that the UI becomes somewhat mangled.

Autolayout Mangled UI

In Interface Builder select the Middle Label, navigate to the size inspector and raise the Horizontal Content Hugging Priority value to 700. Content hugging is the priority with which a view resists being made larger than its intrinsic size.

Horizontal Content Hugging Priority

Select the Middle Button and set the Horizontal Content Compression Resistance value to 100. Content compression Resistance is the priority with which a view resists being made smaller than its intrinsic size.

Horizontal Content Compression Resistance

This combination of content hugging and compression resistance settings will allow your button to make room for the expanding label’s contents.

Height Constraint

Select the top container view and add a new height constraint. Set the Relation to Greater Than ot Equal and the Constant to 84. We’ve chosen 84 as the constant since our button is 44 points tall and we want to kee 20 points of padding above and below the Top Button. This will allow the top container view to reduce in height as needed when the device is in landscape orientation.

Vertical Spacing Constraint

Select the Middle Button and the Make Text Long button and create a new Vertical Spacing constraint between the two. Select the constraint and set the Relation to Greater Than or Equal and the constant to 10. This will allow the space between the buttons to flex as the device is rotated.

View with Content Hugging and Compression Resistance

Landscape View with Content Hugging and Compression Resistance

Build and run the application. Notice that when you tap the Make Text Long button the UI automatically adjusts to accommodate the content. Also notice that the two buttons are still have their centering constraints intact. Rotate the device to make sure the UI adjusts to keep the containerview and the top button on the screen.

Download the Solution Project

Frame Rect vs. Alignment Rect vs. Content Rect vs. intrinsic size

Automatically laying out views on screen is more complicated than simply calculating their frame rectangles. You may have written layout code in the past that has found the drawn rectangle of a given string to figure out how to size views relative to a UILabel. Views will often have a frame that is larger than their drawn contents. How does auto layout know the visual content size of views on the screen for layout? A couple of factors come into play.

Each view has an intrinsicContentSize property that subclasses of UIView can override to let auto layout know of the size of their internal content. This value along with the view’s content hugging and content compression resistance properties allows auto layout to decide how to stretch and shrink views depending the “stuff inside”. Views with a high content hugging priority will tend to stay close to their intrinsic content size. Views with a high content compression resistance priority will resist shrinking below their intrinsic content size as when a UILabel or UIButton begins truncating or clipping its contained text.

Diagram of a UIButton's Intrinsic Content Size

Each view may also override its alignmentRectInsets property. This lets auto layout know that some portion of the view’s content is ornamental and should not be used for purposes of aligning the view to other views. If a view has an ornamental badge, for instance, it would return alignmentRectInsets that tell auto layout to ignore the badge for alignment. The view maintains an intrinsic content size that includes the badge, so that auto layout knows not to draw other views in the area where the badge is drawn.

Diagram of a UIButton With a Badge's Alignment Rect

Y’all come back now, y’hear?

In this tutorial you learned the basics of auto layout. You created a basic view layout in Interface Builder and how to create some basic layout constraints there. Then you ventured into code to remove and then add a constraint that Interface Builder could not provide. In our next auto layout installment, you will learn more about creating constraints in code using the powerful ASCII art visual format language. You will also learn how to animate constraints using Grand Central Dispatch.

Stay tuned!

References

  1. Cocoa Auto Layout Programming Guide
  2. iOS Human Interface Guidelines

Other Resources

  1. WWDC 2012: Introduction to Auto Layout for iOS and OS X
  2. WWDC 2012: Auto Layout by Example
  3. 10 Things You Need To Know About Cocoa Autolayout
  4. Cassowary Constraint Solving Toolkit which is the basis for Cocoa autolayout