Note: When I originally posted the Basic Auto Layout video on YouTube, I recorded it live and did not make a script I could post underneath it. This post makes the same UI as the video, but with a few differences in depth of material and process to illustrate how to handle autolayout errors and a few other auto layout subtleties. I will produce a video on this probably in early February. This will be part of the Swift UI book I am writing, but may also become part of my second book on UI design in iOS.
For most of Xcode’s existence, storyboards were separate for iPads and iPhones. Even among devices, there was a lot of extra work necessary to use the same app in landscape and portrait. With iOS 8 and new phone sizes, this has become a bit more of a problem. We could design a different storyboard for every size and orientation, but that would be an excessive amount of work. Every new version of the phone or tablet would need to be different. If you hear about device fragmentation this is what people are talking about: different devices need different layouts because their screens are different sizes. It’s not just mobile devices that have this problem. Any window for a web page might be a different size. The web page needs to change to handle those differences.
Over that last few years, there are many solutions to adaptive user interfaces, which are interfaces that adapt their size and shape automatically to the device or window they happen to be in. Apple’s solution to this problem is auto layout, which lets the system do the hard work. Using relations between views we describe how to layout the user interface.
In iOS 8, Apple introduced class sizes, a way to describe any device in any orientation. While this removes device fragmentation, class sizes rely heavily on auto layout. Up to iOS 8 you could escape auto layout. Apple changed several methods for user interfaces and view controllers, most notably
presentViewController, in a way that you need auto layout to use them effectively. In this chapter, we’ll discuss basic auto layout and size classes so it will make sense when we need to use them.
If you take a look at Apple’s auto layout documentation, they go on and on about linear equations. This is very confusing and while explaining how it works, it does not get to the functional meat of how to use it. Let’s look at this differently.
We call the relations between views constraints. Basically there are two types of constraints for a view. The first is the size and space between views, usually neighbor views. These we call pins. The second is between two specific views by an attribute of the view. These we call alignment or aligns. Besides aligns and pins, you will need to update and resolve changes to the constraints with resolvers. At the bottom of the storyboard, you will find a few buttons that make your auto layout changes.
For a pin, you select one object and describe how far way it is from its nearest neighbors. All pin operations are found on the pin button. If you were to select a view and open it, you would see this:
The top section sets spacing to the nearest neighbor. If there is no neighbor view, auto layout assumes the superview. On the superview while dragging around a subview you will find blue dotted lines, which are layout guides for the margins. Auto layout will usually base its decisions on these layout guides if your nearest neighbor is the superview.
Below the spacing section in the menu, the sections below that let you set a size. You can set a size specifically for the view using the Width and Height selections. For a size based on the sizes of a neighbor, you can use Equal Widths, Equal Heights and Aspect ratio.
At the very bottom is a drop down selection allowing you to update the view’s frames to match the new constraints. There are time you want to do this and times you do not.
Similar to the pin selections is the alignment selections, which you can reach with the align button:
To use these, you need to select two views, and then set a relationship between them. Note here we align to a common edge or center. The last two selections Horizontal Center in Container and Vertical Center in Container need only one selection, since they will center to the superview. Here too we have a selection to update the frames.
If we do not update the frames immediately, we can update them in the resolver.
While the selections are the same for each half of the resolver, what they affect is very different. The top applies only to selected views, the bottom to all views. Try to avoid using the bottom half, unless you are sure you have all your constraints in place. Views tend to disappear if you use this and you don’t have all your views set. The only one in the bottom half used often is Clear Constraints, which is the best way to handle out-of-control problems with constraints.
Once you get a constraint set up correctly, a selected view will have blue i-beams describing the constraints.
Once you start working with constraints, you will get a lot of warnings. All errors for constraints start as a warning, though they may become fatal errors at run time. You will need to resolve all these errors. There are three types of errors: misplaced views, missing constraints and conflicting constraints.
Misplaced views have the correct constraints but are not in the place they will display at run time. If you select the misplaced view, it will show how far off from the actual location the view is. The storyboard will also show a dotted rectangle giving the correct position. For example this label is a misplaced view.
To resolve a misplaced view you select the misplaced view, and in the selected view section click Update Frames in the resolver. You can avoid this step by using the Update Frames selection in the pin and align popovers. You may not want to do this immediately since there is another kind of error to get rid of first.
A second kind of error is missing constraints. All views using constraints need enough information to determine location and size of a view. In most cases, auto layout can guess at a size if you give enough position information. For example this button is missing constraints:
This button has a constraint to the label so it has a horizontal position, though misplaced. It needs a vertical position too. To fix these errors, add some more constraints. For the button, you might align to the top of the label, or pin to the top margin.
The third type of error is the most difficult: too many constraints. For a button, suppose I pin to the top twice: once 50 points from the top margin and once 53 points from the top margin. The view can’t be in two places at the same time. This causes a conflicting constraint error, which shows as red i-beam in the storyboard.
To correct a conflicting constraint, delete the conflicts until there is only one constraint. If we clicked on the red constraint above that says 50, and then press the Delete key, the constraints will be correct.
With the above explanation, let’s try to put together a user interface using auto layout. We’ll use three buttons, a label and a text view and make them work properly on any device in both landscape and portrait.
Let’s start with a new project. In Xcode hit Command-Shift-N and make a new single view project named MySizePizzaDemo. Use Universal for the device and set Swift for the Language.
Once loaded, Go to the storyboard. On the bottom tool bar you will see the size classes like this:
Make sure this reads wAny hAny. This is the most generic case, and thus works with everything. Drag onto the storyboard a label with a green background and black text centered. Make the label read Hello Pizza. Then add a button with a red background with white text titled Done. Add two more buttons with a blue background and white lettering. Title one Cheese and the other Pepperoni. Add a text view with the default black text on white background. Set the superview’s background to black to see everything better.
Arrange the buttons so they look like this:
To start using constraints, we will align the text field to the center of the superview. Select the text view and then click the alignment button. Check Horizontal Center in Container and Vertical Center in Container, and select items of new Constraints like this:
Your results are not very exciting. The text field almost disappears. This is why you might want to wait until you have set all your constraints to update a frame. In this case we have no size, so auto layout assumes a size of 0 height and 0 width. Click the pin button and set the Width to 175 and the height to 175. Again update the frames for Items of New Constraints. Our text field shows up again.
For the label, let’s pin everything. Select the label a pin to the top, left and right margin with a value of 0. Set the height to 36pt like this:
To turn on the i-beam in the menu, click on one of the dotted i-beams. When entering numbers into the fields be sure to press tab after each entry or the value will revert to its previous value. This time everything is set on this one page. Update frames with Items of New Constraints. The storyboard should look like this:
You might have noticed we didn’t specify a width. That was intentional. We pinned the leading and training edges of the label to the left and right margins, and let auto layout figure out the width for the device it will display on. The width will act as a rubber band – stretching and compressing to the width of the display.
We could set up constraints the same way for the Done button as we did with the label. There is another way to make a constraint which can be helpful. Using control-drag while on the storyboard brings up a modal menu of all possible constraints in this case. Select the Done button. Control-drag straight down from the button until your cursor is over the black of the superview. You get this menu:
You can click to select one constraint, or shift-click to select more than one constraint. Click Bottom Space to Bottom Layout Guide.
There is a drawback to this way of making constraints. It does not set the values correctly — control-drag just keeps your current values. You need to go to the size inspector to set the values for the constraint. In the right panel on Xcode click the ruler button, which is next to the property inspector.
About halfway down, you will find the constraint:
This tells us the constraint has a value of 29, or it is 29 points away from the bottom layout guide. We want it to be 10 points away. Click the Edit button in the constraint, and change the Constant to 10.
Press Return after you make the change. The Done button will respond with a misplacement error. Since we have not set enough constraints yet, we will wait to update the frame.
Select the Done button if not already selected. Control-drag directly left into the superview. You will get another menu, different than the first. These menus are context sensitive, and give only what applies to the situation.
Select Leading Space to Container Margin. Now edit the constant to be -16. Usually we set this to the margin which is 16 points from the edge of the screen. In our case we want to start at the edge of the screen, so we use a constraint of -16.
From the selected Done button, Control-drag up and to the right. You get this menu.
Diagonal drags will take both directions dragged as their context.By dragging up and to the right we got trailing and top space constraints. We only need a horizontal trailing space this time. Click Trailing Space to Container Margin. Change the constraint constant to 0 so we are flush with the margin.
We have all our constraints, but we can see they are misplaced:
With the Done button still selected, click the resolver menu and in the top part for Selected views, click Update Frames.
Our Done button now looks right, and we have blue i-beams.
Select the Pepperoni button. Using the pin menu set the top to 10 the right to 10, and the left to 0. Set the height to 36. Update the frames as well like this:
We get something we did not expect:
The pin menu always relates to the nearest neighbor. The Pepperoni button started overlapping the label. Once one view overlaps another, it is no longer a neighbor. The nearest neighbor heading upward is the top margin, so it placed itself 10 points from the top margin, overlapping the label even more. Since we placed one constraint between Cheese and Pepperoni, auto layout guesses at the vertical placement of Cheese.
There are several ways to fix this. One is prevention. If we dragged the Pepperoni button down slightly before pinning, it would not overlap the button. Our pin would have worked. Another option is change the constant for the Top Edge to Container Margin to 46 points so we have our button 10 points below the label. I’m not a fan of this. If our label height changes for some reason, we have the same overlap problem again.
The third solution is to make a new constraint between the Pepperoni button and the label. This is where control-drag is the most useful. When you need to make a constraint between two views that are not neighbors use control-drag.
To prevent a conflict, delete the current constraint by going to the size inspect and clicking on the left side of the constraint:
Press Delete on the keyboard. It disappears. Now select the Pepperoni button. Control-drag from the Pepperoni button into the label. You will see the label highlight. Release the mouse button. The following menu appears.
Select Vertical Spacing which is the same as pinning to the top. In the size inspector, you’ll see the constraint Top Space to: Hello Pizza!! has a constant of -26. Edit the constraint to have a constant of 10.
Update the frame if necessary. You may find this change automatically updates.
We have one more button to go. We already have a horizontal alignment, we just need a vertical. We will align the top of the Cheese button to the top of the Pepperoni button. Select the cheese button and then shift select the Pepperoni button. Click the align menu and select Top Edges. Keep the value at 0. Update frames to new constraints like this:
With that change, we now have satisfied all constraints and have no errors. It still doesn’t look great. We need to make a few more changes.
The first change is to fill the middle space with the text view. Select the text view. In the resolver, Clear Constraints for selected views. This is the easiest way to avoid conflicting constraints — get rid of all of them. Click the pin menu. Set the bottom to 10, and the left and right to 0. Don’t update the frames.
We have a slight problem here using pin for all four directions. Directly above the text view is the label. if we pinned the top with 10. It would pin to the label with 10, overlapping the buttons below. We want the text view to pin to the buttons, so we skip that pin and will use a control-drag to set it. But we have no top constraint or a height. Auto layout will guess the height is 0, and the view disappears if we update frames now. This is why we didn’t update views this time.
Control-drag from the text view to the Pepperoni button. Select Vertical Spacing. You will get a blue vertical i-beam. Place your cursor in the beam and it will highlight. Click once on the i-beam. This is another way to edit the constant. You will see in the size inspector only information about this constraint. change the constant to 10 like this:
Now the constraint shows a misplaced error. Select the text view again and for the selected views update the frames.
The buttons on the top are bit too small. They should be bigger and more consistent in size. Two constraints help with this: Equal Widths and Equal Heights. These two will take the size constraints of one view and use it for other views. To make them work correctly we need one more constraint on the Cheese button. Pin the right side of Cheese to 0 and update the frames.
The Cheese button stretches across the superview. Control-drag from the Pepperoni button to the Cheese button. Holding down shift, select both Equal Widths and Equal Heights.
The two buttons now are the same size.
I’d like all my buttons to be the same height. I’d also like them a little taller than they are now for easy entry. Let’s add one last constraint between the Pepperoni and the Done button. Since the text view is in the way, control-drag from Pepperoni to the Done button. Select Equal Heights on the menu that shows up. Select the Pepperoni button again. In the size inspector, find the height constraint and change it to 64:
If necessary, update frames. Your storyboard should look like this:
The square story board does not look like any device I’m familiar with. What this will look like on a device we can get by using the simulator, though that requires building and running the simulator. A faster way during the layout stage is to use the preview mode in the assistant editor.
Click on the assistant editor. At the bottom of the drop down menu that usually shows you will find a Preview selection.
Select the Main.Storyboard and the current view appears for a iPhone 4 inch.
At the bottom of each preview is the name of the device. If you float your cursor over the device name, a small icon for rotation appears.
Click it and the phone rotates. It may be too big for the panel. On a touch pad, pinch to change the scale until you can see the preview
On the lower left side of the preview, there is a + button to add more devices to your preview. Click the + button
A list of possible previews appears. You can select any of these you wish. If you want to see both landscape and portrait at the same time, you can select the same device twice and rotate one.
Usually you’ll want an iPhone and iPad preview, since these may be different. As we’ll learn more in the next sections on class sizes, you can customize for each size class and orientation. It becomes handy to see every change you make.
If you make a mistake, the preview pane gets too busy, or need to get rid of a preview device for any other reason, click on the name of the device to select it, then press the Delete key on your keyboard to remove the device.
For those using localization, on the bottom right you will find a button you can use to preview in the different localization languages. Click the default language, and a list of languages appear. One interesting selection for testing is the Double Length Pseudo-language, which doubles all your text to give you an idea how an interface will look with a language that uses a lot of characters.
With these basics, you can lay out most user interfaces. There are more features and ways to use auto layout. We’ll cover some of those in other lessons.