A little runtime excursion

在本自动布局的第1部分教程中你看到老“springs-and-struts”模式让用户界面无法轻易解决布局问题。自动布局的解决方案,但因为这技术是如此强大,这也是一个更加棘手的使用。在第二部分和自动布局教程系列的最后一部分,您将不断地学习所有关于约束和如何应用它们!

A little runtime excursion

This Auto Layout tutorial begins with a very simple app that looks like this:
RW autolayout ios9 2015-09-06 at 6.59.12 PM

它有两个按钮,背景颜色设置这样更容易看到自己的边界。它们之间的按钮有一个数量的限制。如果你已经在按照前面的部分你可以继续使用你现有的应用程序。简单的把其他两个按钮从画布上。如果你从零开始,创建一个新的迅速iPhone应用程序使用单一视图应用程序模板和禁用大小类通过故事板的文件检查器:

RW auto layout iOS9 2015-09-06 at 7.01.03 PM

拖两个按钮进入现场,给他们一个黄色和绿色的背景色。把下面的黄色按钮绿色按钮。选择绿色按钮并利用销菜单约束其最近的邻居(40分),然后选择较低的按钮(8分)做同样的事情。使用对齐菜单水平地中心黄色按钮在容器中,再一次的左边缘对齐两个按钮。在Interface Builder中玩这是不错,但在运行时看看这是如何工作的。ViewController.swift添加以下方法:

@IBAction func buttonTapped(sender: UIButton) {
  if sender.titleForState(.Normal) == "X" {
    sender.setTitle("A very long title for this button", forState: .Normal)
  } else {
    sender.setTitle("X", forState: .Normal)
  }
}

一长一短标题和标题之间的切换按钮,触发事件。这个动作方法连接到两个按钮在界面构建器。从每个按钮Ctrl-drag视图控制器并选择buttonTapped:弹出。运行应用程序并点击按钮来看看它的表现。执行测试在肖像和景观方向。

AutoLayoutSwiftUpdate2

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 select the Trailing Edges option. 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 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 Pin\Equal Widths. This adds a new constraint to both buttons:

RW auto layout ios9 2015-09-06 at 7.17.35 PM

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:

RW auto layout ios9 2015-09-06 at 7.18.23 PM

 

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

AutoLayoutSwiftUpdate7

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:

RW auto layout ios9 2015-09-06 at 7.42.32 PM

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:

RW auto layout ios9 2015-09-06 at 7.44.02 PM

 

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 7 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:

Screen Shot 2015-09-06 at 7.47.44 PM

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

Screen Shot 2015-09-06 at 7.48.33 PM

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 Swift iPhone project using the Single View Application template and name it “Gallery”.

Open Main.storyboard. Disable Size classes, because for the purposes of this tutorial it’s better to see an iPhone-shaped layout in the storyboard editor. Select the view controller and in the Attributes inspector, choose a Size of iPhone 3.5-inch. 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):

SwiftAutoLayout14

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 own UIView or UIControl subclass.

 

Select the green view and open the Pin menu. The popup appears that lets you add a variety of constraints, and you will also see a checkbox labeled Constrain to margins:

RW auto layout ios9 2015-09-06 at 8.25.21 PM

 

记住当你拖动视图出现的蓝色指南附近的边缘,它的父视图?而不是创建一个约束的距离边缘的视图中,您可以创建一个视图的边缘距离约束的0。一个视图可以定义自己的利润大小,你这允许更灵活的布局。对于本教程中,我们将坚持对边缘进行约束的视图。去取消那个盒子,创建绿色的4约束所有四个视图的最近的邻居在每个方向:

RW auto layout ios9 2015-09-06 at 8.28.25 PM

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 the values to match the ones above exactly. Click Add 4 Constraints to finish.

Your storyboard should now look something like this:

RW auto layout iOS 9 2015-09-06 at 8.29.14 PM

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

RW auto layout ios9 2015-09-06 at 8.29.43 PM

Instead it stops at the status bar. But since 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.

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:

RW auto layout ios9 2015-09-06 at 8.31.17 PM

The width of the green view is calculated by the formula “width of superview minus (80 + 80)” and its height by the formula “height of superview minus (13 + 163)”. 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, so the formula changes with it.

 

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.

RW auto layout iOS 9 2015-09-06 at 8.34.44 PM

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:

RW autolayout iOS 9 2015-09-06 at 8.35.54 PM

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 that looks like this at the top:

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:0x7f8deac8a910 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7f8deae26b50(667)]>"
)
 
Will attempt to recover by breaking constraint....

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:

RW auto layout ios9 2015-09-06 at 8.36.49 PM

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. If you add the lengths of the Horizontal Space and Width constraints, then you should also end up at the width of the super view. Like wise for the vertical constraints.

But when you rotate the device to landscape, the math is off and Auto Layout doesn’t know what to do.

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:

RW auto layout iOS 9 2015-09-06 at 8.39.33 PM

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.

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 at run time, Auto Layout spits out a detailed error message when something is wrong. You can 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.

RW auto layout iOS 92015-09-06 at 8.42.21 PM

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 Pin button and just select the T-bar at the bottom:

RW iOS 9 auto layout 2015-09-06 at 8.43.14 PM

Now add a constraint to center the label horizontally with the Alignment menu. Put a checkbox in front of Horizontal Center in Container and then click Add 1 Constraint. The storyboard should now look like this:

RW auto layout iOS 9 2015-09-06 at 8.44.57 PM

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:

RW auto layout iOS 9 2015-09-06 at 8.46.32 PM

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:

RW auto layout iOS 9 2015-09-06 at 8.47.17 PM

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

RW auto layout iOS 9 2015-09-06 at 8.48.12 PM

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:

RW auto layout iOS 9 2015-09-06 at 8.49.01 PM

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:

RW auto layout iOS 9 2015-09-06 at 8.49.49 PM

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

Screen Shot 2015-09-06 at 8.50.42 PM

Adding Images

Next, 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:

RW auto layout iOS 9 2015-09-06 at 8.56.11 PM

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:

RW auto layout iOS 9 2015-09-06 at 8.57.27 PM

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. As the name suggests, this value determines how resistant a view is to being compressed, or shrunk. A higher value here means the view will be less likely to be compressed and more likely to stay the same.

Similarly, the Content Hugging Priority determines how resistant a view is to being expanded. You can imagine “hugging” here to mean “size to fit” – the bounds of the view will “hug” or be close to the intrinsic content size. A higher value here means the view will be less likely to grow and more likely to stay the same.

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, set the vertical Content Hugging Priority to 252. When the superview changes size, that means the image view will be the one to resize, and the label will stay pretty much the same size.

RW auto layout iOS 9 2015-09-06 at 8.58.26 PM

Use the Resolve Auto Layout Issues button and choose Update Frames to fix any lingering issues with the labels placement. 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.

RW autolayout iOS 9 2015-09-06 at 8.59.35 PM

To fix this, use the Resolve Auto Layout Issues button and choose Update Constraints. Previously you used Update 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:

RW auto layout iOS 9 2015-09-06 at 9.01.49 PM

We are now going to start adding more views to show more people. Before you do this, change the simulated size of the view controller to iPhone 4-inch. Then, select the green view and tap ⌘D to duplicate it. Move the duplicate into the top-right corner:

RW auto layout iOS 9  2015-09-06 at 9.03.59 PM

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 (make sure to uncheck Constrain to margins).

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:

RW auto layout iOS 9 2015-09-06 at 9.08.38 PM

Those are some good-looking programmers! :-)

Run the app on a 4-inch iPhone simulator (iPhone 5 or 5/s). It looks good in portrait, but not so much in landscape:
RW autolayout iOS 9 2015-09-06 at 9.11.34 PM

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 fixed Width and fixed Height 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:

RW autolayout iOS 9 2015-09-06 at 9.13.06 PM

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 Heights and then press Add 6 Constraints.

RW auto layout iOS 9 2015-09-06 at 9.17.54 PM

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

RW auto layout iOS 9 2015-09-06 at 9.16.40 PM

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

Ctrl drag from the Ray box to the Matthijs box and choose Horizontal Spacing from the popup 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 the same Ctrl drag technique.

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

Gallery app laid out correctly in landscape

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…

To continue with more advanced topics, check out our Auto Layout video tutorial series, which covers everything you need to become an Auto Layout master. You might also be interested in our Adaptive Layout series as well.

If you have any questions or comments as you continue on you auto layout journey, please join the forum discussion below!

posted @ 2015-11-28 17:31  LoyaltyProgram  阅读(502)  评论(0编辑  收藏  举报