Beginning Auto Layout in iOS 6: Part 2/2

Update note: Check out our newer version of this tutorial, updated to Swift and iOS 8: Beginning Auto Layout Tutorial in Swift: Part 2. This tutorial is an abbreviated version of one of the chapters from our new book iOS 6 By Tutorials. Matthijs Hollemans wrote this – the same guy who wrote the iOS […] By Matthijs Hollemans.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Fixing the width

The Pin menu has an option for Widths Equally. If you set this constraint on two views, then Auto Layout will always make both views equally wide, based on which one is the largest. Let’s play with that for a minute.

Select both buttons and choose Pin\Widths Equally. This adds a new constraint to both buttons:

Buttons widths equally

Note: If you get an extra unintended constraint between one of the buttons and the superview, select the two buttons and select Align\Horizontal Centers again.

You have seen this type of constraint before, in the first part of this tutorial. It looks like the usual T-bar but in the middle it has a circle with an equal sign.

In the Document Outline this shows up as a single Equal Widths constraint:

Equal widths in document outline

Changing the label text on one button will now change the size of the other one as well.

Change the bottom button’s label to “X”, just to make it really small. You will notice that the top button no longer fits its text:

Top button text no longer fits

So how does Interface Builder know which button’s size to use for both of them? If you pay close attention, you’ll see that a Width constraint was added to the button with the truncated text:

Width constraint on truncated button in document outline

Width constraint on truncated button

Interface Builder does this to force the button to become smaller than what it would ideally be, in order to comply with the Equal Widths constraint.

Obviously this is not what you want, so select the top button and choose Size to Fit Content from the Editor menu (or press Cmd =). Now the text fits inside the button again – or rather, the button fits around the text – and the Width constraint is gone.

Run the app and tap the buttons. The buttons always have the same width, regardless of which one has the largest label:

Buttons equal widths in app

Of course, when both labels are very short, both buttons will shrink equally. After all, unless there is a constraint that prevents, buttons will size themselves to fit their content exactly, no more, no less. What was that called again? Right, the intrinsic content size.

Intrinsic Content Size

Before Auto Layout, you always had to tell buttons and other controls how big they should be, either by setting their frame or bounds properties or by resizing them in Interface Builder. But it turns out that most controls are perfectly capable of determining how much space they need, based on their content.

A label knows how wide and tall it is because it knows the length of the text that has been set on it, as well as the font size for that text. Likewise for a button, which might combine the text with a background image and some padding for the rounded corners.

The same is true for segmented controls, progress bars, and most other controls, although some may only have a predetermined height but an unknown width.

This is known as the intrinsic content size, and it is an important concept in Auto Layout. You have already seen it in action with the buttons. Auto Layout asks your controls how big they need to be and lays out the screen based on that information.

You can prevent this by setting an explicit Width or Height constraint on a control. If you resize the control by hand, then Interface Builder will set such an explicit constraint for you. With the Size to Fit Content command, you remove any fixed Width or Height constraints and let the control determine its intrinsic content size again.

Usually you want to use the intrinsic content size, but there are some cases where you may not want to do that. Imagine what happens when you set an image on a UIImageView if that image is much larger than the screen. You usually want to give image views a fixed width and height and scale the content, unless you want the view to resize to the dimensions of the image.

So what happens when one of the buttons has a fixed Width constraint on it? Buttons calculate their own size, but you can override this by giving them a fixed width. Select the top button and choose Pin\Width from the menu. This adds a solid T-bar below the button:

Button fixed width constraint

Because this sort of constraint only applies to the button itself, not to its superview, it is listed in the Document Outline below the button object. In this case, you have fixed the button to a width of 73 points.

Run the app and tap the buttons. What happens? The button text does change, but it gets truncated because there is not enough room:

Button text clipped

Because the top button has a fixed-width constraint and both buttons are required to be the same size, they will never shrink or grow.

Note: You probably wouldn’t set a Width constraint on a button by design – it is best to let the button use its intrinsic size – but if you ever run into a layout problem where you expect your controls to change size and they don’t, then double check to make sure Interface Builder didn’t sneak a fixed Width constraint in there.

Play around with this stuff for a bit to get the hang of pinning and aligning views. Get a feel for it, because not everything is immediately obvious. Just remember that there must always be enough constraints so that Auto Layout can determine the position and size for all views.

Got enough constraints

Gallery example

You should now have an idea of what constraints are and how you can build up your layouts by forging relationships between the different views. In the following sections, you will see how to use Auto Layout and constraints to create layouts that meet real-world scenarios.

Let’s pretend you want to make an app that has a gallery of your favorite programmers. It looks like this in portrait and landscape:

The Gallery app

The screen is divided into four equal quarters. Each quarter has an image view and a label. How would you approach this?

Let’s start by setting up the basic app. You can use your existing “Constraints” app by deleting the buttons and reusing the view.

Or, you can create a new project using the Single View Application template and name it as you like, for instance, “Gallery”. This will just use a nib, so disable the storyboards option.

Open ViewController.xib. From the Object Library, drag a plain view object onto the canvas. Resize the view so that it is 160 by 230 points, and change its background color to be something other than white (I made mine green):

View with auto layout

This view has four constraints to keep it in place. Unlike a button or label, a plain UIView does not have an intrinsic content size. There must always be enough constraints to determine the position and size of each view, so this view also needs constraints to tell it what size it needs to be.

You may wonder, where are these size constraints? In this case, the size of the view is implied by the size of the superview. The constraints in this layout are two Horizontal Spaces and two Vertical Spaces, and these all have fixed lengths. You can see this in the Document Outline:

Constraints for UIView in document outline

The width of the green view is calculated by the formula “width of superview minus (109 + 51)” and its height by the formula “height of superview minus (153 + 77)”. The space constraints are fixed, so the view has no choice but to resize. When you rotate the app, the dimensions of the superview change from 320×460 to 480×300. Plug this new width and height into these formulas, and you’ll get the new size of the green view.

You can see this for yourself when you run the app and flip to landscape, but you can also simulate it directly in Interface Builder.

Select the top-most view in the nib and go to the Attributes inspector. Under the Simulated Metrics section, change Orientation to Landscape:

Simulated metrics landscape

This gives you an instant preview of what the nib’s layout will look like in landscape orientation. The green view has resized in order to satisfy its Horizontal and Vertical Space constraints.

Switch back to portrait orientation.

Note: There are two main reasons why you would drop a plain UIView onto a nib: a) You’re going to use it as a container for other views, which helps with organizing the content of your nibs; or b) It is a placeholder for a custom view or control, and you will also set its Class attribute to the name of your own UIView or UIControl subclass.

You may not always want your UIView to resize when the device rotates, so you can use constraints to give the view a fixed width and/or height. Let’s do that now. Select the green view and from the Pin menu, choose Width. Select the view again and choose Pin\Height.

You have now added two new constraints to the view, a 160 point Width constraint and a 230 point Height constraint:

Width and height constraints on UIView

Because Width and Height apply to just this view, they are located in the Document Outline under the View itself. Usually, constraints express a relationship between two different views – for example, the Horizontal and Vertical Space constraints are between the green view and its gray superview – but you can consider the Width and Height constraints to be a relationship between the view and itself.

Run the app. Yup, looks good in portrait. Now flip over to landscape. Whoops! Not only does it not look like you wanted – the view has changed size again – but the Xcode debug pane has dumped a nasty error message:

Gallery[68932:11303] Unable to simultaneously satisfy constraints.
	Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSLayoutConstraint:0x754dac0 V:[UIView:0x754e510(230)]>",
    "<NSLayoutConstraint:0x754eac0 V:|-(77)-[UIView:0x754e510]   (Names: '|':UIView:0x754e3a0 )>",
    "<NSLayoutConstraint:0x754ea40 V:[UIView:0x754e510]-(153)-|   (Names: '|':UIView:0x754e3a0 )>",
    "<NSAutoresizingMaskLayoutConstraint:0x7558cd0 h=-&- v=-&- UIView:0x754e3a0.width == UIWindow:0x71156e0.width - 20>",
    "<NSAutoresizingMaskLayoutConstraint:0x74128b0 h=--- v=--- H:[UIWindow:0x71156e0(320)]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x754dac0 V:[UIView:0x754e510(230)]>

Break on objc_exception_throw to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

Remember when I said that there must be enough constraints so that Auto Layout can calculate the positions and sizes of all the views? Well, this is an example where there are too many constraints. Whenever you get the error “Unable to simultaneously satisfy constraints”, it means that your constraints are conflicting somewhere.

Let’s look at those constraints again:

Conflicting constraints

There are six constraints set on the green view, the four Spacing constraints you saw earlier and the new Width and Height constraints that you have just set on it. So where is the conflict?

Well, in portrait mode there shouldn’t be a problem because the math adds up. The width of the superview is 320 points. If you add the lengths of the Horizontal Space constraints and the Width of the view, then you should also end up at 320. The way I have positioned the view, that is: 51 + 160 + 109 = 320 indeed. Likewise, the vertical constraints should add up to 460.

But when you rotate the device to landscape, the window (and therefore the superview) is 480 points wide. That means 51 + 160 + 109 + ? = 480. There are 160 extra points that need to go somewhere in that equation and Auto Layout doesn’t know where to get them. Likewise for the vertical axis.

The conflict here is that either the width of the view is fixed and one of the margins must be flexible, or the margins are fixed and the width must be flexible. So one of these constraints has to go. In the above example, you want the view to have the same width in both portrait and landscape, so the trailing Horizontal Space has got to go.

Remove the Horizontal Space at the right and the Vertical Space at the bottom. The nib should look like this:

Conflicting constraints fixed

Now the view has just the right number of constraints to determine its size and position, no more, no less. Run the app and verify that the error message is gone and that the view stays the same size after rotating.

Note: Even though Interface Builder does its best to prevent you from making invalid layouts, it cannot perform miracles. At least Auto Layout spits out a detailed error message when something is wrong. You will learn more about analyzing these error messages and diagnosing layout problems in “Intermediate Auto Layout” in iOS 6 by Tutorials.

Contributors

Over 300 content creators. Join our team.