본문 바로가기
모바일개발(Mobile Dev)/IOS개발(ObjectC)

Auto Layout Tutorial

by 테크한스 2015. 12. 22.
반응형


written by http://www.raywenderlich.com/50317/beginning-auto-layout-tutorial-in-ios-7-part-1



Beginning Auto Layout Tutorial in iOS 7: Part 1

 Matthijs Hollemans 

iOS 7 Feast

Note from Ray: Tutorial Team member Matthijs Hollemans (the iOS Apprentice Series author) has ported this tutorial to iOS 7 as part of the iOS 7 feast. We hope you enjoy!

Have you ever been frustrated trying to make your apps look good in both portrait and landscape orientation? Is making screen layouts that support both the iPhone and iPad driving you to the brink of madness? Despair no longer, I bring you good news!

It’s not hard to design a user interface for a screen that is always guaranteed to be the same size, but if the screen’s frame can change, the positions and sizes of your UI elements also have to adapt to fit into these new dimensions.

Until now, if your designs were reasonably complex, you had to write a lot of code to support such adaptive layouts. You will be glad to hear that this is no longer the case – iOS 6 brings an awesome new feature to the iPhone and iPad: Auto Layout. Xcode 5 and iOS 7 make it even better! If you tried Auto Layout in Xcode 4 and gave up, then you really should give Xcode 5 another chance.

Not only does Auto Layout makes it easy to support different screen sizes in your apps, as a bonus it also makes internationalization almost trivial. You no longer have to make new nibs or storyboards for every language that you wish to support, and this includes right-to-left languages such as Hebrew or Arabic.

This Auto Layout tutorial shows you how to get started with Auto Layout using Interface Builder. In iOS 6 by Tutorials, we take this tutorial even further, and then have an entirely new chapter that builds on this knowledge and shows you how to unleash the full power of Auto Layout via code.

Note: We are in the process of updating all of the chapters in iOS 6 by Tutorials to iOS 7 – and this is a sneak preview of that update! When we’re done, the update will be a free download to all iOS 6 by Tutorials PDF customers.

So grab a snack and your favorite caffeinated beverage, and get ready to become an Auto Layout master!

The problem with springs and struts

You are no doubt familiar with autosizing masks – also known as the “springs and struts” model. The autosizing mask determines what happens to a view when its superview changes size. Does it have flexible or fixed margins (the struts), and what happens to its width and height (the springs)?

For example, with a flexible width the view will become proportionally wider if the superview also becomes wider. And with a fixed right margin, the view’s right edge will always stick to the superview’s right edge.

The autosizing system works well for simple cases, but it quickly breaks down when your layouts become more intricate. Let’s look at an example where springs and struts simply don’t cut it.

Open Xcode 5 and create a new iPhone project based on the Single View Application template. Call the app “StrutsProblem”:

Project options

Click on Main.storyboard to open it in Interface Builder. Before you do anything else, first disable Auto Layout for this storyboard. You do that in the File inspector, the first of the six tabs:

Disable autolayout

Uncheck the Use Autolayout box. Now the storyboard uses the old struts-and-springs model.

Note: Any new nib or storyboard files that you create with Xcode 4.5 or better will have Auto Layout activated by default. Because Auto Layout is an iOS 6-and-up feature only, if you want to use the latest Xcode to make apps that are compatible with iOS 5, you need to disable Auto Layout on any new nibs or storyboard files by unchecking the “Use Autolayout” checkbox.

Drag three new views onto the main view and line them up like this:

Design of the StrutsProblem app

For clarity, give each view its own color so that you can see which is which.

Each view is inset 20 points from the window’s borders; the padding between the views is also 20 points. The bottom view is 280 points wide and the two views on top are both 130 points wide. All views are 254 points high.

Run the app on the iPhone Retina 4-inch simulator and rotate the simulator to landscape. That will make the app look like this, not quite what I had in mind:

Landscape looks bad

Note: You can rotate the simulator using the Hardware\Rotate Left and Rotate Right menu options, or by holding down  on your keyboard and tapping the left or right arrow keys.

Instead, you want the app to look like this in landscape:

What landscape is supposed to look like

Obviously, the autosizing masks for all three views leave a little something to be desired. Change the autosizing settings for the top-left view to:

Autosizing top-left view

This makes the view stick to the top and left edges (but not the bottom and right edges), and resizes it both horizontally and vertically when the superview changes its size.

Similarly, change the autosizing settings for the top-right view:

Autosizing top-right view

And for the bottom view:

Autosizing bottom view

Run the app again and rotate to landscape. It should now look like this:

Landscape still looks bad

Close, but not quite. The padding between the views is not correct. Another way of looking at it is that the sizes of the views are not 100% right. The problem is that the autosizing masks tell the views to resize when the superview resizes, but there is no way to tell them by how much they should resize.

You can play with the autosizing masks – for example, change the flexible width and height settings (the “springs”) – but you won’t get it to look exactly right with a 20-point gap between the three views.

Why?!?!?

To solve this layout problem with the springs and struts method, unfortunately you will have to write some code.

UIKit sends several messages to your view controllers before, during and after rotating the user interface. You can intercept these messages to make changes to the layout of your UI. Typically you would overrideviewWillLayoutSubviews to change the frames of any views that need to be rearranged.

Before you can do that, you first have to make outlet properties to refer to the views to be arranged.

Switch to the Assistant Editor mode (middle button on the Editor toolset on the Xcode toolbar) and Ctrl-drag from each of the three views onto ViewController.m:

Ctrl-drag outlet property

Connect the views to these three properties, respectively:

@property (weak, nonatomic) IBOutlet UIView *topLeftView;
@property (weak, nonatomic) IBOutlet UIView *topRightView;
@property (weak, nonatomic) IBOutlet UIView *bottomView;

Add the following code to ViewController.m:

- (void)viewWillLayoutSubviews
{
    if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
    {
        CGRect rect = self.topLeftView.frame;
        rect.size.width = 254;
        rect.size.height = 130;
        self.topLeftView.frame = rect;
 
        rect = self.topRightView.frame;
        rect.origin.x = 294;
        rect.size.width = 254;
        rect.size.height = 130;
        self.topRightView.frame = rect;
 
        rect = self.bottomView.frame;
        rect.origin.y = 170;
        rect.size.width = 528;
        rect.size.height = 130;
        self.bottomView.frame = rect;
    }
    else
    {
        CGRect rect = self.topLeftView.frame;
        rect.size.width = 130;
        rect.size.height = 254;
        self.topLeftView.frame = rect;
 
        rect = self.topRightView.frame;
        rect.origin.x = 170;
        rect.size.width = 130;
        rect.size.height = 254;
        self.topRightView.frame = rect;
 
        rect = self.bottomView.frame;
        rect.origin.y = 295;
        rect.size.width = 280;
        rect.size.height = 254;
        self.bottomView.frame = rect;
    }
}

This callback occurs when the view controller is rotating to a new orientation. It looks at the orientation the view controller has rotated to and resizes the views appropriately – in this case with hardcoded offsets based on the known screen dimensions of the iPhone. This callback occurs within an animation block, so the changes in size will animate.

Don’t run the app just yet. First you have to restore the autosizing masks of all three views to the following, or the autosizing mechanism will clash with the positions and sizes you set on the views inviewWillLayoutSubviews:

Autosizing off

That should do it. Run the app and flip to landscape. Now the views line up nicely. Flip back to portrait and verify that everything looks good there as well.

It works, but that was a lot of code you had to write for a layout that is pretty simple. Imagine the effort it takes for layouts that are truly complex, especially dynamic ones where the individual views change size, or the number of subviews isn’t fixed.

Now try running the app on the 3.5-inch simulator. Whoops. The positions and sizes of the views are wrong because the hardcoded coordinates in viewWillLayoutSubviews are based on the dimensions of the 4-inch phone (320×568 instead of 320×480). You could add another if-statement that checks the screen size and uses a different set of coordinates, but you can see that this approach is becoming unworkable quickly.

There must be another way

Note: Another approach you can take is to make separate nibs for the portrait and landscape orientations. When the device rotates you load the views from the other nib and swap out the existing ones. But this is still a lot of work and it adds the trouble of having to maintain two nibs instead of one. This approach is quite impractical when you’re using storyboards instead of nibs.

Auto Layout to the rescue!

You will now see how to accomplish this same effect with Auto Layout. First, remove viewWillLayoutSubviewsfrom ViewController.m, because you’re going to do this without writing any code.

Select Main.storyboard and in the File inspector panel, check the Use Autolayout box to enable Auto Layout for this storyboard file:

Enable autolayout

Note: Auto Layout is always enabled for the entire nib or storyboard file. All the views inside that nib or storyboard will use Auto Layout if you check that box.

Run the app and rotate to landscape. It now looks like this:

Landscape looks bad with Auto Layout

Let’s put Auto Layout into action. Hold down the  key while you click on the two views on the top (the green and yellow ones), so that both are selected. From Xcode’s Editor menu, select Pin\Widths Equally:

Pin widths equally

Select the same two views again and choose Editor\Pin\Horizontal Spacing. (Even though the two views appear selected after you carry out the first Pin action, do note that they are in a special layout relationship display mode. So you do have to reselect the two views.)

The storyboard now looks like this:

Storyboard after adding horizontal space constraint

The orange “T-bar” shaped things represent the constraints between the views. So far you added two constraints: an Equal Widths constraint on both views (represented by the bars with the equals signs) and a Horizontal Space constraint that sits between the two views. Constraints express relationships between views and they are the primary tool you use to build layouts using Auto Layout. It might look a bit scary, but it is actually quite straightforward once you learn what it all means.

To continue building the layout for this screen, perform the following steps. Each step adds more orange T-bars.

For the view on the left, choose from the Editor\Pin menu:

  • Top Space to Superview
  • Leading Space to Superview

For the view on the right, choose:

  • Top Space to Superview
  • Trailing Space to Superview

And for the big view at the bottom:

  • Leading Space to Superview
  • Trailing Space to Superview
  • Bottom Space to Superview

You should now have the following constraints:

Constraints after performing the steps

Notice that the T-bars are still orange. That means your layout is incomplete; Auto Layout does not have enough constraints to calculate the positions and sizes of the views. The solution is to add more constraints until they turn blue.

Hold down  and select all three views. From the Editor menu, choose Pin\Heights Equally.

Now select the top-left corner view and the bottom view (using ⌘ as before), and choose Editor\Pin\Vertical Spacing.

Interface Builder should show something like this:

The constraints are valid

The T-bars have become blue. Auto Layout now has enough information to calculate a valid layout. It looks a bit messy but that’s because the Equal Widths and Equal Heights constraints take up a lot of room.

Run the app and… voila, everything looks good again, all without writing a single line of code! It also doesn’t matter which simulator you run this on; the layout works fine on 3.5-inch as well as 4-inch devices.

Landscape now looks good with Auto Layout

Cool, but what exactly did you do here? Rather than requiring you to hard-code how big your views are and where they are positioned, Auto Layout lets you express how the views in your layout relate to each other.

You have put the following relationships – what is known as constraints – into the layout:

  • The top-left and top-right views always have the same width (that was the first pin widths equally command).
  • There is a 20-point horizontal padding between the top-left and top-right views (that was the pin horizontal spacing).
  • All the views always have the same height (the pin heights equally command).
  • There is a 20-point vertical padding between the two views on top and the one at the bottom (the pin vertical spacing).
  • There is a 20-point margin between the views and the edges of the screen (the top, bottom, leading, and trailing space to superview constraints).

And that is enough to express to Auto Layout where it should place the views and how it should behave when the size of the screen changes.

Well done

You can see all your constraints in the Document Outline on the left. The section named Constraints was added when you enabled Auto Layout for the storyboard. (If you don’t see the outline pane, then click the arrow button at the bottom of the Interface Builder window.)

If you click on a constraint in the Document Outline, Interface Builder will highlight where it sits on the view by drawing a white outline around the constraint and adding a shadow to it so that it stands out:

Selected constraint

Constraints are real objects (of class NSLayoutConstraint) and they also have attributes. For example, select the constraint that creates the padding between the two top views (it is named “Horizontal Space (20)” but be sure to pick the correct one) and then switch to the Attributes inspector. There you can change the size of the margin by editing the Constant field.

Constraint attributes

Set it to 100 and run the app again. Now the margin is a lot wider:

There is a wider margin between the views

Auto Layout is a lot more expressive than springs and struts when it comes to describing the views in your apps. In the rest of this tutorial, you will learn all about constraints and how to apply them in Interface Builder to make different kinds of layouts.

How Auto Layout works

As you’ve seen in the test drive above, the basic tool in Auto Layout is the constraint. A constraint describes a geometric relationship between two views. For example, you might have a constraint that says:

“The right edge of label A is connected to the left edge of button B with 20 points of empty space between them.”

Auto Layout takes all of these constraints and does some mathematics to calculate the ideal positions and sizes of all your views. You no longer have to set the frames of your views yourself – Auto Layout does that for you, entirely based on the constraints you have set on those views.

Before Auto Layout, you always had to hard-code the frames of your views, either by placing them at specific coordinates in Interface Builder, by passing a rectangle into initWithFrame:, or by setting the view’s frame,bounds or center properties.

For the app that you just made, you specifically set the frames to:

Struts coordinates

You also set autosizing masks on each of these views:

Struts autosizing masks

That is no longer how you should think of your screen designs. With Auto Layout, all you need to do is this:

Auto Layout instead of struts

The sizes and positions of the views are no longer important; only the constraints matter. Of course, when you drag a new button or label on to the canvas it will have a certain size and you will drop it at a certain position, but that is only a design aid that you use to tell Interface Builder where to put the constraints.

Designing like you mean it

The big advantage of using constraints is that you no longer have to fiddle with coordinates to get your views to appear in the proper places. Instead, you can describe to Auto Layout how the views are related to each other and Auto Layout will do all the hard work for you. This is called designing by intent.

When you design by intent, you’re expressing what you want to accomplish but not necessarily how it should be accomplished. Instead of saying: “the button’s top-left corner is at coordinates (20, 230)”, you now say:

“The button is centered vertically in its superview, and it is placed at a fixed distance from the left edge of the superview.”

Using this description, Auto Layout can automatically calculate where your button should appear, no matter how big or small that superview is.

Other examples of designing with intent (and Auto Layout can handle all of these instructions):

“These two text fields should always be the same size.”
“These two buttons should always move together.”
“These four labels should always be right-aligned.”

This makes the design of your user interfaces much more descriptive. You simply define the constraints, and the system calculates the frames for you automatically.

You saw in the first section that even a layout with just a few views needs quite a bit of work to layout properly in both orientations. With Auto Layout you can skip all that effort. If you set up your constraints properly, then the layout should work without any changes in both portrait and landscape.

Another important benefit of using Auto Layout is internationalization. Text in German, for example, is infamous for being very long and getting it to fit into your labels can be a headache. Again, Auto Layout takes all this work out of your hands, because it can automatically resize your labels based on the content they need to display – and have everything else adapt with constraints.

Adding support for German, French, or any other language is now simply a matter of setting up your constraints, translating the text, and… that’s it!

French

The best way to get the hang of Auto Layout is to play with it, so that’s exactly what you will do in the rest of this tutorial.

Note: Auto Layout is not just useful for rotation; it can also easily scale your UI up and down to accommodate different screen sizes. It is no coincidence that this technology was added to iOS at the same time that the iPhone 5 and its taller screen came out! Auto Layout makes it a lot easier to stretch your apps’ user interfaces to fill up that extra vertical space on the iPhone 5. And with Dynamic Type in iOS 7, Auto Layout has become even more important. Users can now change the global text size setting — with Auto Layout this is easy to support in your own apps.

Courting constraints

Close your current project and create a new iPhone project using the Single View Application template. Name it “Constraints”. Any new projects that you create with Xcode 5 automatically assume that you will be using Auto Layout, so you do not need to do anything special to enable it.

Click on Main.storyboard to open Interface Builder. Drag a new Button onto the canvas. Notice that while you’re dragging, dashed blue lines appear. These lines are known as the guides:

Guides

There are guides around the margins of the screen, as well as in the center:

Other examples of guides

If you have used Interface Builder before, then you have no doubt seen these guides. They are helpful hints that make it easier to align stuff.

In Xcode 4 with Auto Layout enabled, the guides had a different purpose. You still used them for alignment, but they also told you where the new constraints would go. If you dropped the button in the top-left corner against the blue guides, the storyboard in Xcode 4 would look like this:

Button with guides

There are two blue thingies attached to the button. These T-bar shaped objects are the constraints. No matter where you placed your UI controls in Xcode 4’s Interface Builder, they always were given valid constraints. That sounded like a good idea in theory but in practice it made Auto Layout incredibly hard to use from Interface Builder.

Fortunately, that has changed for the better with Xcode 5. After you drop the button into the canvas, there are no T-bars to be seen:

There are no constraints on the button

Notice that there is no Constraints section in the Document Outline pane either. Conclusion: this button does not have any constraints set on it.

But how can this work? You just learned that Auto Layout always needs enough constraints to determine the size and position of all the views, but here you have no constraints at all. Surely this is an incomplete layout?

This is where Xcode 5 is a big improvement over Xcode 4: it no longer forces you to always have a valid layout.

Note: It is a bad idea to run an app with an invalid layout because Auto Layout cannot properly compute where the views should go. Either the positions of the views will be unpredictable (not enough constraints) or the app will crash (too many constraints). Yikes!

Xcode 4 tried to prevent this from happening by making sure there were always exactly enough constraints to create a valid layout. Unfortunately, it often did this by removing your own constraints and replacing them by constraints you did not actually want. That could be very frustrating and many developers gave up on Auto Layout for this reason.

Xcode 5 is not nearly as rude. It allows you to have incomplete layouts while you’re editing the storyboard but it also points out what you still need to fix. Using Interface Builder to create Auto Layout-driven user interfaces has become a lot more fun — and a lot less time-consuming — with Xcode 5.

If you don’t supply any constraints at all, Xcode automatically assigns a set of default constraints, known as theautomatic constraints. It does this at compile time when your app is built, not at design time. Auto Layout in Xcode 5 works hard to stay out of your way while you’re designing your user interfaces, and that’s just how we like it.

The automatic constraints give your views a fixed size and position. In other words, the view always has the same coordinates as you see in the storyboard. This is very handy because it means you can largely ignore Auto Layout. You simply don’t add constraints if the default ones are sufficient and only create constraints for those views that need special rules.

OK, let’s play around a bit with constraints and see what they can do. Right now, the button is in the top-left corner and has no constraints. Make sure the button is aligned with the two corner guides.

Add two new constraints to the button using the Editor\Pin menu, so that it looks like this:

Constraints for the button in the top-left corner

If you hadn’t guessed already, that is the Leading Space to Superview and the Top Space to Superviewoptions.

All the constraints are also listed in the Document Outline pane on the left-hand side of the Interface Builder window:

Button constraints in document outline

There are currently two constraints, a Horizontal Space between the button and the left edge of the main view, and a Vertical Space between the button and the top edge of the main view. The relationship that is expressed by these constraints is:

“The button always sits at 20 points from the top-left corner in its superview.”

Note: These aren’t actually very useful constraints to make because they’re the same as the automatic ones. If you always want your button to be relative to the top-left corner of its superview, then you might as well not provide any constraints at all and let Xcode make them for you.

Now pick up the button and place it in the scene’s top-right corner, again against the blue guides:

Button moved to top-right corner

Whoa, what has happened here? In Xcode 4 this would have broken the old constraints and assigned new ones based on the blue guides, but in Xcode 5 the button keeps the existing constraints. The problem here is that the size and position of the button in Interface Builder no longer correspond with the size and position that Auto Layout expects based on the constraints. This is called a misplaced view.

Run the app. The button will still appear in the top-left corner of the screen:

Button is still in the top-left corner at runtime

When it comes to Auto Layout, orange is bad. Interface Builder drew two orange boxes: one with a dashed border and one with a solid border. The dashed box displays the view’s frame according to Auto Layout. The solid orange box is the view’s frame according to how you placed it in the scene. These two should match up, but here they don’t.

How you fix this depends on what you want to achieve:

  • Do you want the button to be attached to the left edge of the screen at a distance of 254 points? In that case you need to make the existing Horizontal Space constraint 234 points bigger. That’s what the orange badge with “+234” means.
  • Do you want the button to be attached to the right edge of the screen instead? Then you need to remove the existing constraint and create a new one.

Delete the Horizontal Space constraint. First select it in the canvas or in the Document Outline, and then press the Delete key on your keyboard.

Button with only vertical space constraint

Notice that this turns the Vertical Space constraint orange. Until now it was blue. There is nothing wrong with that particular constraint; it just means there are not enough constraints left to determine the complete position of the button. You still need to add a constraint for the X-position.

Note: You may be wondering why Xcode does not add an automatic constraint for the X-position. The rule is that Xcode only creates automatic constraints if you did not set any constraints of your own. As soon as you add a single constraint, you tell Xcode that you’re now taking responsibility for this view. Xcode will no longer make any automatic constraints and expects you to add any other constraints this view needs.

Select the button and choose Editor\Pin\Trailing Space to Superview. This puts a new constraint between the right edge of the button and the right edge of the screen. This expresses the relationship:

“The button always sits at 20 points from the top-right corner in its superview.”

Run the app and rotate to landscape. Notice how the button keeps the same distance from the right screen edge:

The button in landscape

When you place a button (or any other view) against the guides and make a constraint, you get a spacing constraint with a standard size that is defined by the “HIG”, Apple’s iOS Human Interface Guidelines document. For margins around the edges, the standard size is a space of 20 points.

Now drag the button over to the left a little:

Misplaced button

Again you get a dashed orange box because the view is misplaced. Let’s say this new button position is indeed what you want. It’s not uncommon to make a constraint and then nudge the view by a few pixels, making the orange boxes appear. One way to fix this is to remove the constraint and make a new one, but there is an easier solution.

The Editor menu has a Resolve Auto Layout Issues submenu. From that menu, choose Update Constraints. In my case, this tells Interface Builder it should make the constraint 64 points larger, as so:

Misplaced button fixed

Great, the T-bars turn blue again and the layout is valid. In the Document Outline, you can see that the Horizontal Space constraint no longer has a standard space:

Larger horizontal space in document outline

So far you’ve played with Horizontal Space and Vertical Space constraints. There is also a “center” constraint. Drag a new Button object to the bottom center of the canvas, so that it snaps into place with the guides:

Drag button to bottom center

To keep the button always center-aligned with its superview, on the horizontal axis, you need to add a Center X Alignment constraint. From the Editor menu choose Align\Horizontal Center in Container. This adds a long orange line:

Center X alignment constraint

The line is orange because you’ve only specified what happens to the X-coordinate of the button, not its Y-coordinate. Use the Editor\Pin menu to add a Vertical Space constraint between the button and the bottom of the view. It should look like this:

Enough constraints for the button

In case you didn’t know how, it is the Bottom Space to Superview option. The Vertical Space constraint keeps the button away from the bottom of the view (again, using the standard margin).

Run the app and rotate it to landscape. Even in landscape mode, the button stays at the bottom center of the screen:

Button stays at bottom center in landscape

That’s how you express intent: “This button should always be at bottom center.” Notice that nowhere did you have to tell Interface Builder what the button’s coordinates are, only where you want it anchored in the view.

With Auto Layout, you’re no longer supposed to care about the exact coordinates of where you place your views on the canvas or what their size is. Instead, Auto Layout derives these two things from the constraints that you set.

You can see this paradigm shift in the Size inspector for the button, which is now quite different:

Different size inspectors

With Auto Layout disabled, typing into the X, Y, Width or Height fields will change the position and size of the selected view. With Auto Layout enabled you can still type new values into these fields, but if you already have constraints set on the view it may now become misplaced. You also have to update the constraints to make them match the new values.

For example, change the Width value of the button to 100. The canvas turns into something like this:

After changing the button width

Xcode 4 would have replaced the Center X Alignment constraint with a Horizontal Space and a new constraint on the button itself that forces it to have a width of 100 points. However, Xcode 5 simply says, “It’s fine with me if you want the width to be 100 points but just so you know, that’s not what the constraints say.”

In this case you do want the button to be 100 points wide. There is a special type of constraint for this: the Fixed Width constraint. First press Undo so that the button is centered again and the T-bars are all blue. Select the button and choose Editor\Pin\Width. This puts a new T-bar below the button:

Fixed width constraint on button

Select that T-bar and in the Attributes inspector change Constant to 100. This forces the button to always be 100 points wide, no matter how large or small its title. To see this a bit better you can give the button a background color:

Larger width on button

You can also see this new Width constraint in the Document Outline on the left:

Width constraint in document outline

Unlike the other constraints, which are between the button and its superview, the Width constraint only applies to the button itself. You can think of this as a constraint between the button and… the button.

You may wonder why the button did not have a Width constraint before. How did Auto Layout know how wide to make the button without it?

Here’s the thing: the button itself knows how wide it must be. It calculates this based on its title text plus some padding. If you set a background image on the button, it also takes that into account.

This is known as the intrinsic content size. Not all controls have this, but many do (UILabel is another example). If a view can calculate its own preferred size, then you do not need to set specific Width or Height constraints on it. You will see more of this later.

I am not fat

To return the button to its optimal size, first remove the Width constraint. Then select the button and chooseSize to Fit Content from the Editor menu. This restores the button’s intrinsic content size.

It takes two to tango

Guides do not appear only between a view and its superview, but also between views on the same level of the view hierarchy. To demonstrate this, drag a new button onto the canvas. If you drag this button close to the others, then their guides start to interact.

Put the new button next to the existing one so that it snaps into place:

Snap two buttons

There are quite a few dotted guidelines here. Interface Builder recognizes that these two buttons can align in different ways – at their tops, centers and baselines.

Xcode 4 would have turned one of these snapping guides into a new constraint. But with Xcode 5, if you want to have a constraint between these two buttons, you have to make it yourself. You’ve seen that you can use theEditor\Pin menu to make a constraint between two views, but there is an easier way too.

Select the new button and Ctrl-drag to the other button, like so:

Ctrl-drag between buttons

When you let go of the mouse button, a popup appears. Choose the first option, Horizontal Spacing.

New constraint popup

This creates a new constraint that looks like this:

Horizontal space between buttons

It is orange, meaning that this button needs at least one other constraint. The size of the button is known — it uses the intrinsic content size — and there is a constraint for the button’s X-position. That leaves only the Y-position without a constraint.

Here the missing constraint is pretty easy to determine but for more complicated designs it may not always be immediately obvious. Fortunately, you don’t have to guess. Xcode has been keeping score and can tell you exactly what is missing.

There is small a red arrow in the Document Outline, next to View Controller Scene. Click that arrow to see a list of all Auto Layout issues:

Auto Layout issues in document outline

Sweet! Let’s add that missing Y-position constraint. Ctrl-drag from the new button downwards:

Ctrl-drag down from button

The popup menu has different options this time. The items in this menu depend on the context — which views are you dragging between — and the direction you moved the mouse. Choose Bottom Space to Bottom Layout.

The new button now has a Vertical Space to the bottom of the screen, but also a Horizontal Space that links it with the other button. Because this space is small (only 8 points), the T-bar may be a bit hard to see, but it is definitely there.

Click on the Horizontal Space (8) constraint in the Document Outline to select it:

Highlighted horizontal space between buttons

When you select a constraint, it lights up the controls it belongs to. This particular constraint sits between the two buttons. What you’ve done here is say:

“The second button always appears on the left of the first one, no matter where the first button is positioned or how big it is.”

Select the button with the yellow background and type something long into its label like “A longer label”. When you’re done, the button resizes to make room for the new text, and the other button shifts out of the way. After all, it is attached to the first button’s left edge, so that is exactly what you intended to happen:

Button with longer label

Just to get a better feel for how this works, play with this some more. Drag another button into the canvas and put it above the yellow one, so that they snap into place vertically (but don’t try to align the left edges of the two buttons):

Snap green button to top of yellow button

Give the new button a background color (green) so you can more easily see its extents.

Because you snapped the two buttons together, there is now a standard space of 8 points between them that is recommended by the HIG. Turn this into a constraint by Ctrl-dragging between the two buttons. Select Vertical Spacing from the popup menu.

Note: The “HIG”, which is short for iOS Human Interface Guidelines, contains Apple’s recommendations for designing good user interfaces. It is mandatory reading for any iOS developer. The HIG explains which UI elements are appropriate to use under which circumstances, and best practices for using them. You can find this document here.

You are not limited to standard spacing between controls, though. Constraints are full-fledged objects, just like views, and therefore have attributes that you can change.

Select the Vertical Space constraint between the two buttons. You can do this in the canvas by clicking the T-bar, although that tends to be a bit finicky. By far the easiest method is to click on the constraint in the Document Outline. Once you have it selected, switch to the Attributes inspector:

Vertical space attributes

Type 40 into the Constant field to change how big the constraint is. Now the two buttons will be further apart, but they are still connected:

Vertical space between buttons is now larger

Run the app and flip to landscape to see the effect:

Larger vertical space in landscape

The buttons certainly keep their vertical arrangement, but not their horizontal one! The reason should be obvious: the green button does not have a constraint for its X-position yet.

Adding a Horizontal Space from the green button to the left edge of the canvas won’t solve this problem. With such a constraint the green button always keeps the same X-coordinate, even in landscape. That doesn’t look very nice, so instead you are going to express the following intention:

“The yellow button will always be horizontally centered, and the green button will align its left edge with the left edge of the yellow button.”

You already have a constraint for the first condition, but not for the second. Interface Builder shows guides for alignment, so you can drag the top button until its left edge snaps with the yellow button:

Snap left edges

If you also dragged the button vertically, the frame of the button and the Vertical Space constraint may no longer agree on the correct distance. You will see an orange badge on the T-bar:

Badge on vertical space

If this happens, simply use the arrow keys to nudge the button into place again until the badge disappears.

Finally, Ctrl-drag between the two buttons and from the popup menu choose Left. This creates an alignment constraint that says: “The left edges of these two views are always aligned”. In other words, the two buttons will always have the exact same X-position. That solves the layout problem and the T-bars turn blue:

Left-align constraint

Run the app and rotate to landscape to verify that it works:

Left-aligned buttons in landscape

Where To Go From Here?

Now that you’ve got your first taste of Auto Layout, how do you like it? It can take a bit of getting used to, but can make your life a lot easier and your apps much more flexible!

Want to learn more? Keep reading for part 2 of this Auto Layout tutorial, where you’ll continue playing with the buttons in Interface Builder to get a better understanding of the possibilities Auto Layout offers — and the problems you may encounter.

And best of all – you will also use Auto Layout to create a realistic layout that you may find in a real app! :]

In the meantime, if you have any questions or comments please join the forum discussion below!

Matthijs Hollemans

Matthijs Hollemans is an independent iOS developer and designer who lives to create awesome software. His focus is on iPad apps because he thinks tablets are way cooler than phones. Whenever he is not glued to his screen, Matthijs plays the piano, is into healthy and natural living, and goes out barefoot running. Visit his website at http://www.hollance.com.


Beginning Auto Layout Tutorial in iOS 7: Part 2

 Matthijs Hollemans 

iOS 7 Feast

Note from Ray: Tutorial Team member Matthijs Hollemans (the iOS Apprentice Series author) has ported this tutorial to iOS 7 as part of the iOS 7 feast. We hope you enjoy!

In part 1 of this Auto Layout tutorial you saw that the old “struts-and-springs” model for making user interfaces cannot easily solve all layout problems. Auto Layout is the solution, but because this technology is so powerful it is also a bit more tricky to use.

Thankfully, Xcode 5 makes Auto Layout a lot easier. If you tried Auto Layout in Xcode 4 and gave up, then we invite you to give it another try with Xcode 5.

In this second part and final part of the Auto Layout tutorial series, you’ll continue learning all about constraints and how to apply them!

A little runtime excursion

This Auto Layout tutorial begins with a very simple app that looks like this:
The starter app

It has two buttons that have their background color set just so it’s clearer to see their boundaries. The buttons have a number of constraints between them. If you’ve been following along with the previous part you can continue using your existing app. Simply remove the other two buttons from the canvas.

If you’re starting from scratch, create a new iPhone application using the Single View Application template. Drag two buttons into the scene and give them a background color. Use the Editor\Pin menu to make a Vertical Spacing constraint between the two buttons (40 points), and a Bottom Space to Superview constraint on the lower button (20 points). Use the Editor\Align menu to center the yellow button horizontally in the container, and again to align the left edges of both buttons.

Playing with this in Interface Builder is all well and good, but let’s see how this works at runtime. Add the following method to ViewController.m:

- (IBAction)buttonTapped:(UIButton *)sender
{
    if ([[sender titleForState:UIControlStateNormal] isEqualToString:@"X"]) {
        [sender setTitle:@"A very long title for this button" 
                forState:UIControlStateNormal];
   } else {
        [sender setTitle:@"X" forState:UIControlStateNormal];
   }
}

This toggles between a long title and a short title for the button that triggered the event. Connect this action method to both of the buttons in Interface Builder. Ctrl-drag from each button to the view controller and selectbuttonTapped: in the popup.

Run the app and tap the buttons to see how it behaves. Perform the test in both portrait and landscape orientations.

Short and long titles

Regardless of which button has the long title and which has the short title, the layout always satisfies the constraints you have given it:

  • The lower button is always center-aligned in the window, horizontally.
  • The lower button always sits 20 points from the bottom of the window.
  • The top button is always left-aligned with the lower button and 40 points above it.

That is the entire specification for your user interface.

For fun, remove the Leading Alignment constraint (select it in the outline pane and press Delete on your keyboard), then select both buttons in Interface Builder and from the Align menu pick Right Edges. Now run the app again and notice the differences.

Repeat, but now choose Align\Horizontal Centers. That will always center the top button with respect to the bottom button. Run the app and see how the buttons act when you tap them. (Remember, if you get a dashed orange box when you change the constraints, you can use the Editor\Resolve Auto Layout Issues menu to update the button frames accordingly.)

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 Editor\Pin\Widths Equally. This adds a new constraint to both buttons:

Buttons with the Equal Widths constraint

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 equals sign.

Even though there are two T-bars, in the Document Outline this shows up as a single Equal Widths constraint:

Equal Widths constraint 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 Auto Layout know which button’s size to use for both of them? If you pay close attention, you’ll see that the top button’s frame is no longer correct:

Top button misplaced frame

Obviously this is not what you want, so select the top button and choose Size to Fit Content from the Editormenu (or press ⌘ =). Now the text fits inside the button again – or rather, the button fits around the text – and due to the Equal Widths constraint the yellow button also resizes.

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

Equal widths buttons in app

Of course, when both labels are very short, both buttons will shrink equally. After all, unless there is a constraint that prevents it, 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.

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.

Usually you want to use the intrinsic content size, but there are some cases where you may not want to do that. You can prevent this by setting an explicit Width or Height constraint on a control.

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 with 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 46 points.

You cannot simply drag the button’s resize handles to make the button wider. If you do, you’ll end up with a whole bunch of orange boxes. Remember that Xcode 5 does not automatically update the constraints for you (unlike Xcode 4). So if you make a change to the button’s frame, it’s up to you to make the constraints match again. The alternative approach is to simply change the constraint instead.

Select the Width constraint and go to the Attributes inspector. Change Constant to 80 to make the button wider:

Width constraint attributes

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 a fixed Width constraint didn’t sneak 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. Create a new iPhone project using the Single View Application template and name it “Gallery”.

Open Main.storyboard. From the Object Library, drag a plain View object onto the canvas. Resize the view so that it is 160 by 284 points, and change its background color to be something other than white (for example, green):

Green view in canvas

Note: There are two main reasons why you would drop a plain UIView onto a storyboard: a) You’re going to use it as a container for other views, which helps with organizing the content of your scenes; 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 ownUIView or UIControl subclass.

Let’s give this view some constraints. You’ve already seen two ways to make constraints: the Editor\Pin andAlign menus, and Ctrl-dragging between views. There is a third method that you’ll use here. At the bottom of the Interface Builder window is a row of buttons:

Auto Layout buttons

The four circled buttons are for Auto Layout. From left to right they are: Align, Pin, Resolve Auto Layout Issues, and Resizing Behavior. The first three perform the same functions as the corresponding items from the Editormenu. The Resizing Behavior button allows you to change what happens to the constraints when you resize views.

Select the green view and click the Pin button. A popup appears that lets you add a variety of constraints:

The pin popup

The Spacing to nearest neighbor section at the top is what you’ll use most often. Click the four T-bar thingies so they become solid red:

Spacing to nearest neighbor

This will create four new constraints between the green view and its superview, one for each side of the view. The actual spacing values may be different for you, depending on where you placed the view. (You don’t have the change these values to match mine). Click Add 4 Constraints to finish.

Your storyboard should now look something like this:

View with the four new constraints

This view needs 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 green view in document outline

The width of the green view is calculated by the formula “width of superview minus (98 + 62)” and its height by the formula “height of superview minus (65 + 199)”. The space constraints are fixed, so the view has no choice but to resize. (Again, your values may be different depending on where you put the view.)

When you rotate the app, the dimensions of the superview change from 320×568 to 568×320. Plug this new width and height into these formulas, and you’ll get the new size of the green view (408×56).

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. Open the Assistant editor (press the button in Xcode’s toolbar that looks like a butler/alien) and select Preview in the jump bar:

Storyboard preview

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

You can leave this preview pane open as you design your UI and it will update automatically. You can also use it to toggle between the 3.5 and 4-inch form factors.

Note: Maybe you wondered why the constraint at the top of the view didn’t go all the way up to the top of the screen:

Space at top

Instead it stops at the status bar. But in iOS 7 the status bar is always drawn on top of the view controller — it is no longer a separate bar — so what gives? When you created the constraint it didn’t actually attach to the top of the screen but to an invisible line called the Top Layout Guide.

On a regular view controller this guide sits at 20 points from the top of the screen, at least when the status bar is not hidden. In a navigation controller it sits below the navigation bar. Because the navigation bar has a different height in landscape, the Top Layout Guide moves with the bar when the device is rotated. That makes it easy to place views relative to the navigation bar. There is also a Bottom Layout Guide that is used for the tab bar and toolbars.

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 click the Pin button; in the popup put checkmarks in front of Width and Height.

Pin popup with width and height selected

Click Add 2 Constraints to finish. You have now added two new constraints to the view, a 160 point Width constraint and a 284 point Height constraint:

Width and height constraints on green view

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 superview – but you can consider the Width and Height constraints to be a relationship between the green 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[39367:a0b] 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:0xc1a1e80 V:[UIView:0xc1a2b10(284)]>",
    "<NSLayoutConstraint:0xc1a36c0 V:[_UILayoutGuide:0xc1a2d20]-(65)-[UIView:0xc1a2b10]>",
    "<NSLayoutConstraint:0xc1a36f0 V:[UIView:0xc1a2b10]-(199)-[_UILayoutGuide:0xc1a3230]>",
    "<_UILayoutSupportConstraint:0xc15dbd0 V:[_UILayoutGuide:0xc1a2d20(20)]>",
    "<_UILayoutSupportConstraint:0xc1a1510 V:|-(0)-[_UILayoutGuide:0xc1a2d20]   (Names: '|':UIView:0xc1a2930 )>",
    "<_UILayoutSupportConstraint:0xc1a3720 V:[_UILayoutGuide:0xc1a3230(0)]>",
    "<_UILayoutSupportConstraint:0xc1a30e0 _UILayoutGuide:0xc1a3230.bottom == UIView:0xc1a2930.bottom>",
    "<NSAutoresizingMaskLayoutConstraint:0x8c6c6a0 h=--& v=--& H:[UIView:0xc1a2930(320)]>"
)
 
Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0xc1a1e80 V:[UIView:0xc1a2b10(284)]>
 
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 (1-4) and the new Width and Height constraints that you have just set on it (5 and 6). So where is the conflict?

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 and Width constraints, then you should also end up at 320. The way I have positioned the view, that is: 98 + 160 + 62 = 320 indeed. Likewise, the vertical constraints should add up to 568.

But when you rotate the device to landscape, the window (and therefore the superview) is 568 points wide. That means 98 + 160 + 62 + ? = 568. There are 248 extra points that need to go somewhere in that equation and Auto Layout doesn’t know where to get them from. 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. You can’t have both. 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 storyboard 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 warn you about invalid layouts, it cannot perform miracles. It will warn you when there are too few constraints but it doesn’t fare so well at detecting layouts with too many constraints. 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.

Painting the portraits

Drag a Label onto the green view. Notice that now the guides appear within that green view, because it will be the superview for the label.

Dragging the label

Position the label against the bottom margin, horizontally centered against the guides. Add a space constraint to anchor the label against the bottom of the green view, at 20 points distance. The quickest way is to use the Pinbutton and just select the T-bar at the bottom:

Pin menu with bottom T-bar selected

Now add a constraint to center the label horizontally. You’ve seen how to do this with the Editor\Align menu but you can also use the Align button from the floating Auto Layout menu. Select the label and click the Align button to bring up the popup:

Align horizontal center

Put a checkbox in front of Horizontal Center in Container and then click Add 1 Constraint. The storyboard should now look like this:

Label constraints

Notice that these two new Horizontal and Vertical Space constraints are listed under the green view’s own Constraints section, not in the main view.

Drag a new Image View object onto the storyboard, and make the layout look like this:

Image view with constraints

The image view is pinned to the top, left, and right edges of its superview, but its bottom is connected to the top of the label with a standard spacing of 8 points. If you’re unsure of how to do this, then follow these steps.

1. Drag the image view into the green view but don’t worry too much about its size or position:

After adding the image view

2. With the image view selected, press the Pin button and choose the following options:

Pin menu for image view

The top, left, and right T-bars are set to 20 points but the bottom one is set to 8 points. Important: For Update Frames you should choose Items of New Constraints. If you had left this to the default of None, the storyboard would look something like this:

Misplaced image view

The constraints you chose result in a different frame than the image view’s current position and size. But if you choose Items of New Constraints, Interface Builder will automatically adjust the frame as it adds the constraints and everything looks dandy:

Image view frame OK

Of course, if you do end up with a misplaced frame, you can use the Resolve Auto Layout Issues button to fix it:

Resolve issues menu

Download the resources for this tutorial and unzip the file. You will find an Images folder – add this folder into your project. Set Ray.png as the image for the image view, change the image view’s mode to Aspect Fit and set its background color to white. Change the label’s text to say “Ray”.

Your layout should now look like this:

Gallery with Ray

Notice that the constraints inside the green view turned to orange. This happened the moment you set the image on the image view. How come your layout is suddenly invalid? Fortunately you can take the guesswork out of it and let Xcode tell you exactly what’s wrong.

Click the small red arrow next to View Controller Scene in the Document Outline to view the issues:

Content priority ambiguity error

You have a Content Priority Ambiguity error. That’s quite the mouthful. This is what it means: If neither the image view nor the label has a fixed height, then Auto Layout doesn’t know by how much to scale each if the height of the green view should change. (Interface Builder seems to ignore for now that the green view actually has a fixed Height constraint set on it.)

Let’s say at some point in your app the green view becomes 100 points taller. How should Auto Layout distribute these new 100 points among the label and the image view? Does the image view become 100 points taller while the label stays the same size? Or does the label become taller while the image view stays the same? Do they both get 50 points extra, or is it split 25/75, 40/60, or in some other possible combination?

If you don’t solve this problem somehow then Auto Layout is going to have to guess and the results may be unpredictable.

The proper solution is to change the “Content Compression Resistance Priority” of the label. You will learn more about that later on. For now, go into the Size inspector for the label and set the vertical Content Compression Resistance Priority to 751. That makes it one higher than the priority of the image view. While you’re at it, setContent Hugging Priority to 252.

Compression resistance priority

The T-bars should turn blue again and the Auto Layout warnings are gone.

Adding the other heads

Drag the green view into the main view’s top-left corner. Recall that the green view had Horizontal Space and Vertical Space constraints that determined its position in the parent view. It still has those and they cause the frame of the view to be misaligned.

Misaligned green view

To fix this, use the Resolve Auto Layout Issues button and choose Update Constraints. Previously you usedUpdate Frames, which moved and resized the view the match the constraints. Here you want to do the opposite: you want the constraints to update to match the frame.

Note that the Vertical Space at the top is now negative. That happens because this constraint is connected to the Top Layout Guide. But there’s no reason why constraints cannot have negative values, so you can leave this as is. (If it bothers you, delete that “Vertical Space (-20)” constraint and pin the view to the top of the window.)

The Horizontal Space now has size 0 and is represented by a thick blue line at the left edge of the window. So even though the view sits completely in the corner, it still needs constraints to anchor it there:

View in top-left corner

Select the green view and tap ⌘D to duplicate it. Move the duplicate into the top-right corner:

Duplicate view in top-right corner

Notice that the T-bars are orange. When you made the duplicate, it apparently lost its constraints for the X and Y position. To fix that, pin the view to the top and the right edges of the window.

Duplicate two more times and put these copies in the bottom-left and bottom-right corners, respectively. Again, pin these views to their corners.

Change the screen design to the following:

Gallery with all 4 heads

Those are some good-looking programmers! :-)

Run the app. It looks good in portrait, but not so much in landscape:

Gallery landscape bad

It should be pretty obvious what went wrong: you’ve set a fixed width and height on the four brightly-colored container views, so they will always have those sizes, regardless of the size of their superview.

Select the Width (160) and Height (284) constraints from all four views and delete them (this is easiest in the Document Outline). If you run the app now, you’ll get something like this:

Still bad in landscape

Note: If you’re wondering why some of the views are larger than others, this is again related to the intrinsic content size. The size of the image determines how large the image view is; the size of the text determines how large the label is. Taken together with the constraints for the margins — 20 points on all sides — this determines the total size of each view.

This looks very much like the problem you solved in the introduction in part 1, so if you think back to how you solved that, you’ll recall that you gave the views equal widths and heights.

Select all four colored views. This is easiest in the Document Outline; hold  and click on the four views. You can add the constraints in one go. In the Pin popup put checkmarks in front of Equal Widths and Equal Heightsand then press Add 6 Constraints.

Add equal widths and heights constraints

Run the app again and rotate the device. Hmm… still no good:

Gallery landscape equal sizes

All the views do have the same height, and they also appear to have the same width, so your constraints are being met. It’s just not the width and height that you want them to have.

Just saying that all four views must have equal sizes is not enough to determine what those sizes should actually be, because Auto Layout does not know how these four views are connected to each other. They appear side-by-side in the design, but there are no actual constraints between them. Auto Layout does not know that it needs to split the window width between the “Ray” and “Matthijs” boxes.

If Auto Layout can’t figure this out by itself, you have to tell it.

To be related

Select the Ray and Matthijs boxes and choose Pin\Horizontal Spacing from the Editor menu. Because the boxes are side-by-side, this adds a Horizontal Space constraint with size 0 between them, and that is enough to let Auto Layout know how these two views are related. Also put a Vertical Space between the Ray and Dennis Ritchie boxes using Editor\Pin\Vertical Spacing.

Run the app again, and this time it looks all right:

Gallery landscape OK

Note: Interface Builder still complains about misplaced views at this point. I’m not sure why that happens but it appears to be a bug in Xcode. If these warnings bother you, then select the main view (or the view controller) and from the Resolve Auto Layout Issues menu choose Update All Frames in View Controller. It does not change how the app works at runtime but at least it makes Xcode happy.

A quick note on the image views: they stretch out because you have not given them a fixed size. You may not know it, but that’s intentional on your part. ☺ The image views wouldn’t fit in landscape mode otherwise. However, if you want an image view to keep its original aspect ratio, then you’re out of luck. You cannot achieve the following effect using Interface Builder:

Gallery aspect ratio

Unfortunately, Interface Builder does not currently provide a way to make constraints that keep the aspect ratio of a view intact. To do that, you need to create and set the constraints programmatically. You will learn how to do that in “Intermediate Auto Layout” in iOS 6 by Tutorials.

Where To Go From Here?

If you’ve made it this far, congratulations – you now know what Auto Layout is all about, and have experimented with the basics! But there’s a lot left to learn…

The tutorial you have just read is only the first half of the Beginning Auto Layout chapter from the book iOS 6 by Tutorials. The second half teaches how to use Auto Layout to create more “real-world” screen layouts, and everything else you need to know about using Auto Layout from Interface Builder.

But like any visual design tool, Interface Builder has its limitations and sometimes it just makes more sense to work with the NSLayoutConstraint objects directly from code. iOS 6 by Tutorials dedicates an entire chapter to this topic, Intermediate Auto Layout. So if you want to get to the bottom of Auto Layout, get the book!

Matthijs Hollemans

Matthijs Hollemans is an independent iOS developer and designer who lives to create awesome software. His focus is on iPad apps because he thinks tablets are way cooler than phones. Whenever he is not glued to his screen, Matthijs plays the piano, is into healthy and natural living, and goes out barefoot running. Visit his website at http://www.hollance.com.



반응형