Auto Layout and Stack Views

Unit 2: Introduction to UIKit|Introduction to UIKit

When you build an app, you want to make sure it looks good on every iOS device. Xcode includes a powerful system called Auto Layout which makes it easy to build intricate interfaces that work on various screen sizes.

Auto Layout relies on constraints, or rules, to dynamically calculate the size and position of all views in a view hierarchy. So your interfaces will look and work the same—no matter what device your users have in their hands or how they're holding it.

In this lesson, you'll learn the fundamentals of Auto Layout for building precisely designed user interfaces.

What You'll Learn

  • How to use Auto Layout to build precise views
  • How to create constraints
  • How to use stack views to simplify Auto Layout
  • How to simplify interface coverage with size classes
  • How to debug layouts using Xcode tools

Vocabulary

Related Resources

Guide

As you begin developing apps for iOS, it's important to think of the different devices that your apps will run on. You've learned that every user interface element has a size and a position on the screen. How might the size and position of those elements change in relation to the size and orientation of the device?

Create a new project called "AutoLayoutPractice" using the iOS template for a Single View App. Open Main.storyboard and use the layout guides to add a Button from the Object Library to the top left corner of the screen.

Interface Builder provides a quick way to check what your interface will look like on different screen sizes. Click the "View as" button (next to the Show/Hide Document Outline button along the bottom button bar) to reveal a menu that lists multiple iOS devices, their interface style, and orientations.1

Interface Builder displaying different devices and orientations

Go ahead and play around with the list. As you choose different screen sizes, styles, and orientations, the canvas redraws your user interface accordingly. If you've positioned a button in the top left of the canvas, it remains in that same position—no matter which device or orientation you've selected.

Why Auto Layout?

Now return the canvas to the iPhone 11 Pro screen size in portrait orientation, then move the button to the center of the screen. Use the blue alignment guides to ensure that the button is exactly in the center of the view.1

Button centered on iPhone 11 Pro

Now switch the orientation to landscape mode.1 You'll see that the button is no longer centered In fact, it isn't even on the screen!2

Button no longer centered when in landscape

What's going on? The button has stayed in the same X/Y position, based on the portrait orientation when you created the button. If you want the button to stay in the exact center, regardless of screen size or orientation, you'll need to create a set of constraints, or rules, that can be used to determine the size and position of your button. This system of using constraints to make adaptive interfaces is called Auto Layout.

Create Alignment Constraints

At the right of the bottom button bar is a set of tools for creating and managing constraints. To lock the button in the center of the screen, you'll create two constraints that define the position of the button:

  • The horizontal center of the button is equal to the horizontal center of the view.
  • The vertical center of the button is equal to the vertical center of the view.

Start by returning to the portrait orientation and ensuring that the button is centered in the view. With the button selected, click the Align button Align tool, the second button in the bottom bar of constraint tools.

A popover displays a list of alignment constraints, which define the relationship between the selected object and the parent view. Select the bottom two options, "Horizontally in Container" and "Vertically in Container," then click "Add 2 Constraints." Once you've created these constraints, you should see crossing blue lines over the top of your button, indicating horizontal and vertical alignment with the parent view.

Creating constraints and viewing list of constraints

Use the "View as" button to select different devices and orientations. You'll see that the alignment constraints you added are keeping the button centered in all views.

Create Size Constraints

What about the width and height of the button? Since there aren't currently any size constraints on the button, its size is dictated by the button text and font. But Interface Builder provides multiple ways to use constraints to set the width and height of interface elements.

Start by using the Attributes inspector to change the background color of the button to something other than clear. This will make the button's size easier to see. 1

Changing button background in Attributes inspector

Now assume you want the height of the button to always be 60, no matter the screen size or orientation of the device. Click the Add New Constraints button Add New Constraints tool, the button immediately to the right of the Align tool.

The popover displays a list of text fields and checkboxes to help you add constraints. Select the Height checkbox and adjust the value to 60. Click "Add 1 Constraint" to create the height constraint.

Adding height constraint using Add New Constraints tool

Note again that if you use the "View as" button to select different devices and orientations, you'll see that the size constraints added are keeping the button size identical in all views.

Constraints Relative to the Screen

You've fixed the height of the button, but what if you want the button's width to vary based on the screen size? Assume you want the button's left and right edges to always be 20 pixels from the left and right edges of the view, respectively.

Click the Add New Constraints button again, and notice the four fields at the top of the popover. These values define the distances from the top, leading, bottom, and trailing edges of the button to the nearest view. In this case, since there are no other views on the screen, they're dictating the distances from the button edges to the view's edges.

Leading refers to the left edge of the screen, while trailing refers to the right edge. These are labeled "leading" and "trailing" instead of "left" and "right" since not all languages read in the same direction. For certain users, you'll want your app flipped. Leading and trailing constraints make this process easier.

Adjust the left and right-edge values to 20. The red indicators will illuminate to show which edges are being constrained. Click "Add 2 Constraints."

Adding left and right edge constraints using Add New Constraints tool

Notice that the button has now expanded to be 20 pixels from each edge of the screen. Use the "View as" button to select different devices and orientations. You'll see that the width of the button varies such that it always maintains the same distance from the edges of the screen. If for some reason the button didn't update, update the button's frame using the Update Frames tool Update Frames button.

Safe Area Layout Guide

Remove the button from the screen by selecting it and pressing the Delete key. Add a label from the Object Library to the top-left corner of the screen.1 Using the Add New Constraints tool, create constraints with values of 0 on the top, left, and right of the label to the view.2

Using the pin tool to add top, leading, and trailing constraints

Now select the label and open the Size inspector. You can view the three constraints that you just added.1

Size inspector showing top, leading and trailing constraints

Notice that the constraints are between the label (which you have selected) and the Superview. This means the edges of the label are constrained to the edges of the main view controller's view. You can see in Interface Builder that this places the label under the status bar and obscures some of the label's text.2 You may be tempted to simply change the value of the top constraint so that the distance from the top is equal to the height of the status bar. However, what if later that view controller is embedded in a navigation controller? The label would no longer be covered by the status bar, but it would then be covered by a navigation bar.

To fix this, you'll need to create constraints relative to the Safe Area of your view controllers. The Safe Area is displayed in Interface Builder as a subview of the view controller's primary view. 1

View navigator with Safe Area view selected

If you select the Safe Area, the entire view controller is selected except for portions of the screen that are taken up by system views like the status bar and home indicator. Interface Builder anticipates the existence of these common system views. However, other system bars can turn up at runtime, and the Safe Area will adjust for those as needed. This allows your content to adapt to system overlays such that it won't be hidden.

Delete your current constraints by selecting them one at a time in the Size inspector and pressing the Delete key. 1

Top constraint to superview

Drag your label a bit lower on the screen so it is below the status bar, and create a constraint between the label's top edge and the top edge of the Safe Area using the Add New Constraints tool. Also add constraints for the leading and trailing edges of the label. Since the Add New Constraints tool constrains your view to the nearest view, creating these constraints with the tool will constrain the top, leading, and trailing edges of the label relative to the Safe Area.1

Top, leading, and trailing constraints relative to Safe Area

Resolve Constraint Issues

Now delete the label that you added. Add a button to the center of the view controller and give it two alignment constraints - one to center it vertically on the screen, and one to center it horizontally on the screen. Now use the Add New Constraints tool to constrain its left edge 20 pixels from the left edge of the screen, and to constrain its right edge 30 pixels from the right edge of the screen.

The red lines on the canvas indicate that there's a problem.1 You've now created two conflicting constraints, each of which is trying to dictate the X position of the button. To see a list of constraint conflicts, click the red indicator in the top-right corner of the document outline.

Constraint conflict list in Document Outline

One of the constraints says, "Set the horizontal center of the button equal to the horizontal center of the view"; and the other says, "The left edge of the button is 20 pixels away from the left edge of the view." Since these two constraints can't coexist, you'll need to remove one.

From the Conflicting Constraints list, click the red error indicator at the top right. Select the constraint that centers the button, and then click Delete Constraints.1

Delete conflicting constraint

From time to time, you may run into a constraint issue that's tricky to resolve. Or maybe you're adding interface elements to the scene and you need to reconfigure the view's layout. In either situation, you can click the Resolve Auto Layout Issues button Resolve Auto Layout Issues button, next to the Add New Constraints tool, and use the options to try to automatically address constraint issues.

Update Constraint Constants will attempt to update the rules to match what is currently displayed in the storyboard scene.

Add Missing Constraints will attempt to add new constraints that match what is currently displayed in the storyboard scene.

Reset to Suggested Constraints will clear all constraints and attempt to accurately assign new constraints that match what is currently displayed in the storyboard scene.

Clear Constraints will remove any rules associated with the position and size of the selected view or all views.

Resolve Constraint Warnings

Move the button you've been working with to a different position in the scene. You should see an Auto Layout warning.1

Auto Layout warning

To understand what's happening, click the yellow indicator in the top-right corner of the document outline. The warning informs you that the position of the button is no longer in sync with the position defined by the constraints.1

Misplaced frame warning in Document Outline

To adjust the button's position to fit the constraints, select the button on the canvas, then click the Update Frames button Update Frames button.

Constraints Between Siblings

So far in this lesson, you've learned how to create constraints that define relationships between a view and its parent view. But there may also be times you want to create constraints between sibling views. In the following exercise, you'll work with multiple labels that display text information about yourself.

Delete the button you've been working with, and add a label to the scene. Double-click the label and type in your name. Drag the label near the top of the view, using the guides to center it horizontally.1

Name label top center

With the label selected, use the Add New Constraints tool to constrain it to be 0 pixels from the top of the Safe Area. To enable the constraint, you may need to select the red indicator line below the top field in the popover.1

Top constraint 0 pixels from status bar

Use the Align tool to align the horizontal center of the label with the center of the parent view.1

Align tool to center label horizontally

With one label set, you can add a second label and base its position on the position of its sibling. Drag another label from the Object library onto the view, just below the existing label. Enter some information about yourself, perhaps one of your favorite hobbies, into the new label's text.

Now assume you want the new label to have the same horizontal center as the first label and to be positioned 20 pixels below it. With the new label selected, click the Add New Constraints button. In the popover, enter 20 into the top text field, and click "Add 1 Constraint."

At this point, Interface Builder will display an error, indicating that the new label has a constraint describing its vertical position, but no information about its horizontal position. Interface Builder can help you resolve this error.

Select both labels in the scene by Command-clicking each of them, and click the Align button. In the popover, select the Horizontal Centers checkbox, set its value to 0, and click "Add 1 Constraint."1 This tells Interface Builder that you want the selected views to have the same center position, with 0 pixels of offset.

If Interface Builder still displays a warning, click the Updates Frames button to update the labels' frames to match the constraints you just specified.

Centering label horizontally to previous label

To practice what you just learned, add three new labels and repeat the above steps three more times to position them on the scene. When you're done, each label should be 20 pixels below the previous one, and they all should be centered horizontally.1

Five labels horizontally centered with spacing

Stack Views

Did you find that last exercise a bit repetitive? Managing the constraints of multiple objects can be tedious, especially when the position of each element is the same distance from the previous one. What if you decide later to have 30 pixels of separation between labels instead of 20? Or what if you want all of the labels to be positioned on the left side of the parent instead of in the center? You would have to spend a lot of time updating each and every constraint.

UIKit provides a smarter approach. Rather than create and update lots of individual constraints, you can use a stack view to automatically manage the constraints of its child views.

A single stack view manages either a row or a column of interface elements, and arranges those elements based on the properties set on the stack view. Start by selecting all the labels you just created, then click the Embed In button Embed In to the right of the Resolve Auto Layout Issues tool and choose Stack View from the list that appears. This organizes the selected views into a single stack and removes any constraints on the individual labels.1

Adding five labels into Stack View

At the moment, your new stack view doesn't have any constraints that define its size or position. To change that, select the stack view, then click the Add New Constraints button. Set the top, leading, and trailing edges of the stack view to 0 pixels from the parent view's edges. (You'll need to select the red indicators associated with each text field to enable the constraints.) Give the stack view a constraint height of 300. Click "Add 4 Constraints," and click the Update Frames button, if necessary.

Adding constraints to Stack View

Stack View Attributes

How a stack view manages views

Now that you have all your labels in a stack, you can learn how to manage the stack view and define the positions of its subviews. But before you begin, set the background of each label to a unique color. This will make it clear how your adjustments are affecting the interface.

Adding background color to each label

Select your stack view from the document outline, and open the Attributes inspector to reveal four main properties. Go ahead and explore these attributes, making adjustments along the way.

  1. Axis determines whether the elements within the view are stacked vertically or horizontally.

  2. Alignment describes how the elements within the stack are positioned. You can choose one of the following:

    • Fill — Each of the elements fills the size of the stack. For example, if the stack view has a width of 320 pixels, each element in a vertical stack will also have a width of 320 pixels.
    • Leading — The leading edge of each element is aligned to the leading edge of the stack view.
    • Center — The center of each element is aligned to the center of the stack view.
    • Trailing — The trailing edge of each element is aligned to the trailing edge of the stack view.
  3. Distribution defines how the elements are distributed within the stack view. You can choose one of the following:

    • Fill — The stack view resizes the arranged subviews so that they fill all the available space along the axis you specified. The stack view sizes the first label as large as possible to completely fill the stack.
    • Fill Equally — The stack view resizes the arranged subviews so that they fill al the available space along the axis. The views are resized so that they are all the same size along the stack view’s axis.
    • Fill Proportionally — The stack view resizes the arranged subviews so that they fill all the available space along the axis. If the size of the stack view changes, the views are resized proportionally to one another. For example, if two labels have heights of 50 and 100, respectively, and you increase the stack view's height by 50 percent, the labels then have heights of 75 and 150.
    • Equal Spacing — The stack view doesn't resize the subviews but positions them at an equal distance from one another.
    • Equal Centering — The stack view doesn't resize the subviews but ensures that the center of each subview is equal distance to the centers of the other subviews.
  4. Spacing describes the amount of space between each element in the stack view. For example, you can change the spacing from 0 to 20 to add 20 points of spacing between labels.

Use the Size inspector to remove the height constraint on the stack view. If a stack view doesn't have a specified height, it will resize based on its subviews.

Removing height constraint on Stack View

Now that you've defined some attributes, try adding new labels to the stack. Or rearrange the existing labels and adjust the stack's spacing and alignment. Imagine if you hadn't used a stack view and you wanted to adjust the interface. How many constraints would you have needed to add, remove, or adjust?

Whenever possible, it's a good practice to use stack views before trying to manage constraints individually. Stack views allow you to create nice-looking interfaces quickly—and also make it easy to modify or customize them in the future.

Size Classes

With so many different combinations of screen sizes and orientations, it's important to build an interface that works well on all iOS devices. In the case of iPad devices, the split-screen feature adds an additional layer of complexity because an app may run in a smaller amount of space than you originally expected. To help simplify user interface development for many different situations, iOS includes size classes.

Each iOS device has a default set of size classes that you can use as a guide when designing your interface. Each dimension, the width and the height, is either the Compact or Regular size class, and the two dimensions form a trait collection. The "View as" button indicates the trait collection of the current device and orientation you’ve selected. For example, if you use the "View as" button to select an iPhone 11 Pro in Portrait mode, you see "(wC hR)" as part of the button title. This indicates that the trait collection for this device is Compact Width, Regular Height.

View as button with size class

Though an iPad has a Regular Width, Regular Height size class in both portrait and landscape, an app does not always run full-screen. Using the split-screen feature, an app can consume one third, one half, or two thirds of the screen real estate, and different-sized iPads will have unique trait collections when multi-tasking. It sounds like a lot to handle, but don't think about the wide array of devices you need to support. Instead, focus on supporting the four different width/height trait collections, and your interface will be correct.

Vary Traits

Constraints and properties can be set to different values depending on the trait collection. Suppose you want a stack view's Spacing property to be 0 most of the time. However, when run on a Regular Width, Regular Height device, it should be set to 20. With Interface Builder, you can easily add varying traits for a particular property or constraint. Using the stack view that you previously created with colored labels, select the stack and open the Attributes inspector. Set its Spacing to 0, and then click the '+' button, which opens a popover for introducing variations.1

Regular width, regular height trait variation

Since you want to add a new variation for the Regular Width, Regular Height trait collection, set the appropriate controls to these values, then select "Add Variation". This will add a new Spacing property that is unique for this particular trait collection. Change this new Spacing to 20.1

Spacing variation of 20

To verify that this trait variation is working properly, click the "View as" button and change the device to an iPad (which is Regular Width, Regular Height). You should notice additional spacing between each label. When you return to an iPhone 11 Pro, the label spacing diminishes.

Installed

Sometimes you'll want to disable particular views or constraints depending on the trait collection. For example, maybe in order to save space, your app's interface should not include the red label when the trait collection's height is set to Compact. To begin implementing this design decision, select the red label and open the Attributes inspector. Near the bottom, you'll find an "Installed" checkbox. This determines whether the particular view or constraint exists within the view hierarchy. Click the '+' button next to the checkbox, and you'll be presented with the same popover to introduce a variation. Set the Width to Any and the Height to Compact, then click "Add Variation". This will add a new Installed property that spans any trait collection that includes a Compact Height.1

Uninstalled view on compact height

Deselect the checkbox for this new property, and build and run your application using the iPhone 11 Pro Simulator. When the device is in Portrait mode (Compact Width, Regular Height), the red label displays within the stack view. When you rotate the device to landscape, the trait collection updates to Regular Width, Compact Height and the red label disappears to save space.

Debug View Hierarchy

The view hierarchy can become very complex, with numerous stack views and an assortment of buttons, labels, and images within different trait collections. If you have issues at runtime where one view is covering another, or constraints are not working as you'd expect, Xcode includes a tool that can help you solve the problem.

Build and run the app you just built that includes the colored labels. After the view has loaded, press the Debug View Hierarchy Debug View Hierarchy button button at the top of the Debug area that extends from the bottom of Xcode. The view will appear in a 3D environment that you can swivel around to see how the view hierarchy was constructed.1

Debug view turned sideways to see three-dimensionally

Here’s a breakdown of what you’re seeing in the editor area. As you move back to front, the proceeding view is layered on top of the previous.

  • The black view is your application's UIWindow
  • The white view is your view controller's view
  • The small clear rectangle is the UIStackView that contains the colored labels
  • The red, green, blue, orange, and purple labels are added subsequently

Using the Debug navigator Debug navigator button on the left, you can see a tree view of your hierarchy, along with the values of each of the constraints.2 This is a much quicker way to get the value of each constraint instead of printing them to the console.

The Debug View Hierarchy tool also includes an assortment of buttons at the top of the Debug area that you can use to enable/disable the content of each view, hide/show constraints, and more. If you find yourself struggling to resolve an issue with a view, you should consider using this tool.

Lab: Calculator

Overview

The objective of this lab is to use Auto Layout to create a view that scales with the size and layout of any screen. You'll use view objects, constraints, and stack views to create a simple calculator that maintains its layout on all device sizes.

Create a new project called "Calculator" using the iOS template for a Single View App.

Step 1: Create the Outer Containers

Calculator in Interface Builder

  • In Interface Builder, set the "View as" device to iPhone 11 Pro.
  • Ignoring the complexity of the buttons for now, it's easiest to break the calculator down into two distinct sections: the top third displays the digits that are entered, and the bottom two-thirds are used for input.
  • Drag a vertical stack view from the Object library onto the scene. Use the Add New Constraints tool to add four constraints that align the top, leading, bottom, and trailing edges of the stack view to the the outer view's respective edges with 0 spacing.1 Click the Update Frames button, and the stack view should cover the entire screen.

Align all edges of stack view

  • Drag two UIView objects from the Object library into the stack view. Control-drag from the bottom view to the outer view, and select Equal Heights in the popup menu. This will set the inner view's height equal to the outer, which you will fix in the next step. 1

Create constraint between bottom view and outer view

  • Select the bottom view and open the Size inspector. Locate the height constraint and double-click it. Change the Multiplier value from 1 to 0.66, which will set the inner view's height equal to the outer view's height, multiplied by 0.66.1 The height of the bottom view will always be two-thirds the size of the view controller's view.

Multiplier adjustment for bottom view

Add a UILabel from the Object library into the top container view. Set the text to "0" and the font size to 30.0, then open the Add New Constraints tool. Add bottom and trailing constraints, with 8 pixels of spacing between them.1

Label alignment in bottom corner

Step 2: Add and Size Buttons

  • The finished calculator has five rows of buttons, where each row has an equal height. You can use another vertical stack view to accomplish this. Drag a vertical stack view into the bottom container view, then use the Add New Constraints tool to constrain the top, leading, bottom, and trailing edges of the stack view to the the bottom container view's respective edges with 0 spacing. Click the Update Frames button, and the stack view should cover the entire bottom container.

  • Select the newly-added vertical stack view, and open the Attributes inspector. Set the "Distribution" property to "Fill Equally" so that all subviews within the stack view will have the same height. Set the "Spacing" to 1, which will create the horizontal separation lines between each row.

  • You can use horizontal stack views for each row of buttons. Add a horizontal stack view into the vertical stack view, and set its "Distribution" property to "Fill Equally" so that all subviews within the stack view have the same width. Set the "Spacing" to 1, which will create the horizontal separation lines between each subview.

  • Add a UIButton into the horizontal stack view. Set the tint color to Dark Text and the background color to Light Gray.

  • Copy the button to your clipboard (Command-C), then paste (Command-V) three additional buttons into the horizontal stack view. Change the background color of the last button to System Red. 1

Four buttons added to horizontal stack view

  • Select the horizontal stack view in the Document Outline, and copy it to your clipboard (Command-C). Then paste (Command-V) four additional horizontal stack views into the vertical stack view. 1

Four rows of buttons added to vertical stack view

  • The bottom horizontal stack view uses only three buttons instead of four. Since some buttons are sized differently than others, you need to update the "Distribution" of this stack view to "Fill Proportionally". Then, delete one of the light gray buttons from the bottom so the stack view contains the proper number of controls. 1

Three buttons in proportional stack view

  • Control-drag from the bottom-leftmost light gray button to the button next to it, and then select "Equal Widths." This ensures that these two buttons are always the same size. Repeat the process for the middle light gray button and the red button. 1 Then select the leftmost gray button, and open the Size inspector. Double-click its width constraint and change the multiplier to 2. 2 This ensure that the left gray button is twice the width of the other gray button.

One button with twice the width of other button

  • Finally, update the top three buttons to use a dark gray color, and update the button titles to match those on a traditional calculator.

Congratulations! You've used both constraints and stack views to create a simple calculator. Be sure to build and run your application on multiple iOS devices to verify that the constraints you've defined make sense on all screen sizes. Save your work to your project folder.