Introduction to Auto Layout - iOS Programming: The Big Nerd Ranch Guide (2014)

iOS Programming: The Big Nerd Ranch Guide (2014)

15. Introduction to Auto Layout

In this chapter, you will return to Homepwner and universalize it so that it will run on an iPad as well as on an iPhone. You will then use the Auto Layout system to ensure that Homepwner’s detail interface appears as you want no matter what type of device it is running on.

Universalizing Homepwner

Currently, Homepwner can be run on the iPad simulator, but it will not look right.

Figure 15.1 An iPhone application running on a simulated iPad

An iPhone application running on a simulated iPad

This is not what you want for your iPad users. You want Homepwner to run natively on the iPad so that it will look like an iPad app. A single application that runs natively on both the iPad and the iPhone is called a universal application.

Reopen your Homepwner project. In the project navigator, select the Homepwner project (the item at the top of the file list). Then select the Homepwner target in the project and targets list and the General tab. This tab presents a convenient interface for editing some of the target’s properties.

Locate the Deployment Info section and change the Devices pop-up from iPhone to Universal (Figure 15.2).

Figure 15.2 Universalizing Homepwner

Universalizing Homepwner

Homepwner is now a universal application. To see the difference, select an iPad simulator from the scheme pop-up menu and then build and run the application. Homepwner looks much better. The table view and its rows are sized appropriately for the iPad screen.

Figure 15.3 A universal application running on the iPad

A universal application running on the iPad

Now add a new item to get to the detail interface. Here the results are not so good:

Figure 15.4 Detail interface does not automatically scale

Detail interface does not automatically scale

The table view interface knew how to resize itself for the iPad sized-screen, but your custom detail interface needs some guidance. You will provide that guidance using Auto Layout.

The Auto Layout System

In Chapter 4, you learned that a view’s frame specifies its size and position relative to its superview. So far, you have defined the frames of your views with absolute coordinates either programmatically or by configuring them in Interface Builder. Absolute coordinates, however, make your layout fragile because they assume that you know the size of the screen ahead of time.

Using Auto Layout, you can describe the layout of your views in a relative way that allows the frames to be determined at runtime so that the frames’ definitions can take into account the screen size of the device that the application is running on.

The screen size for each device is listed in Table 15.1. (Remember that points are used when laying out your interface and map to physical pixels on the device’s screen. A retina device has the same size screen in points as a non-retina device, even though it has twice as many pixels.)

Table 15.1 Device Screen Sizes

Device

Width x Height (points)

iPhone/iPod (4S and earlier)

320 x 480

iPhone/iPod (5 and later)

320 x 568

All iPads

768 x 1024

Alignment rectangle and layout attributes

The Auto Layout system works with yet another rectangle for a view – the alignment rectangle. This rectangle is defined by several layout attributes (Figure 15.5).

Figure 15.5 Layout attributes defining an alignment rectangle of a view

Layout attributes defining an alignment rectangle of a view

Width/Height

These values determine the alignment rectangle’s size.

Top/Bottom/Left/Right

These values determine the spacing between the given edge of the alignment rectangle and the alignment rectangle of another view in the hierarchy.

CenterX/CenterY

These values determine the center point of the alignment rectangle.

Baseline

This value is the same as the bottom attribute for most, but not all, views. For example, UITextField defines its baseline to be the bottom of the text it displays rather than the bottom of the alignment rectangle. This keeps “descenders” (letters like ‘g’ and ‘p’ that descend below the baseline) from being obscured by a view right below the text field.

Leading/Trailing

These values are used with text-based views like UITextField and UILabel. If the language of the device is set to a language that reads left-to-right (e.g., English), then the leading attribute is the same as the left attribute and the trailing attribute is the same as the right attribute. If the language reads right-to-left (e.g., Arabic), then the leading attribute is on the right and the trailing attribute is on the left.

By default, every view in a XIB file has an alignment rectangle, and every view hierarchy uses Auto Layout. But, as you have seen in your detail view, the default will not always work as you want. This is when you need to step in.

You do not define a view’s alignment rectangle directly. You do not have enough information (screen size!) to do that. Instead, you provide a set of constraints. Taken together, these constraints allow the system to determine the layout attributes, and thus the alignment rectangle, for each view in the view hierarchy.

Constraints

A constraint defines a specific relationship in a view hierarchy that can be used to determine a layout attribute for one or more views. For example, you might add a constraint like “the vertical space between these two views should always be 8 points” or “these views must always have the same width.” A constraint can also be used to give a view a fixed size, like “this view’s height should always be 44 points.”

You do not need to have a constraint for every layout attribute. Some values may come directly from a constraint; others will be computed by the values of related layout attributes. For example, if a view’s constraints set its left edge and its width, then the right edge is already determined (left edge + width = right edge, always).

If, after all of the constraints have been considered, there is still an ambiguous or missing value for a layout attribute, then there will be errors and warnings from Auto Layout and your interface will not look as you expect on all devices. Debugging these problems is important, and you will get some practice later in this chapter.

How do you come up with constraints? Let’s see how using the view in the BNRDetailViewController’s view hierarchy that will be simple to constrain – the toolbar.

First, describe what you want the view to look like regardless of screen size. For the toolbar, you could describe it like this:

· The toolbar should sit at the bottom of the screen.

· The toolbar should be as wide as the screen.

· The toolbar’s height should be 44 points. (This is Apple’s standard for instances of UIToolbar.)

To turn this description into constraints in Interface Builder, it will help to understand how to find a view’s nearest neighbor. The nearest neighbor is the closest sibling view in the specified direction (Figure 15.6).

Figure 15.6 Nearest neighbor

Nearest neighbor

If a view does not have any siblings in the specified direction, then the nearest neighbor is its superview, also known as its container.

Now you can spell out the constraints for the toolbar:

1. The toolbar’s bottom edge should be 0 points away from its nearest neighbor (which is its container – the view of the BNRDetailViewController).

2. The toolbar’s left edge should be 0 points away from its nearest neighbor.

3. The toolbar’s right edge should be 0 points away from its nearest neighbor.

4. The toolbar’s height should be 44 points.

If you consider the second and third constraints, you can see that there is no need to explicitly constrain the toolbar’s width. It will be determined from the constraints on the toolbar’s left and right edges. There is also no need to constrain the toolbar’s top edge. The constraints on the bottom edge and the height will determine the value of the top attribute.

Now that you have a plan for the toolbar, you can add these constraints. Constraints can be added using Interface Builder or in code.

Apple recommends that you add constraints using Interface Builder whenever possible. This is what you will do in this chapter. However, if your views are created and configured programmatically, then you can add constraints in code. In Chapter 16, you will get a chance to practice that approach.

Adding Constraints in Interface Builder

Open BNRDetailViewController.xib. First, select the image view in the canvas and delete it from the XIB file. You will recreate the image view and add its constraints programmatically in Chapter 16.

Select the toolbar on the canvas. At the bottom righthand corner of the canvas, find the Auto Layout constraint menu (Figure 15.7).

Figure 15.7 Selecting a constraint

Selecting a constraint

Click the Selecting a constraint icon (the second from the left) to reveal the Pin menu. This menu shows you the current size and position of the toolbar. You can add all of the necessary constraints for the toolbar in this menu (Figure 15.8).

Figure 15.8 Adding 4 constraints to the toolbar

Adding 4 constraints to the toolbar

At the top of the Pin menu are four values that describe the toolbar’s current spacing from its nearest neighbor on the canvas. For the toolbar, you are only interested in the bottom, left, and right values. They are all 0, meaning that these edges of the toolbar are currently 0 points away from the toolbar’s nearest neighbor in those directions. The toolbar has no siblings to its bottom, left, or right, so its nearest neighbor in all three directions is its container, the view of the BNRDetailViewController.

To turn these values into constraints, click the orange struts separating the values from the square in the middle. The struts will become solid lines.

In the middle of the menu, find the toolbar’s Height. It is currently 44 points, which is what you want. To constrain the toolbar’s height based on this value, check the box next to Height. The button at the bottom of the menu reads Add 4 Constraints. Click this button.

Build and run the application on the iPad simulator. Create an item and select it to navigate to the detail interface. The toolbar will appear at the bottom of the screen and be as wide as the screen (Figure 15.9).

Figure 15.9 Toolbar appearing correctly

Toolbar appearing correctly

You can see the constraints that you just added in the dock to the left of the canvas. Find the Constraints section and reveal its contents. However, you will see only three constraints here. The fourth constraint, the fixed height of the toolbar, is in a separate Constraints section underneath Toolbar. Reveal the contents of this Constraints section (Figure 15.10).

Figure 15.10 Constraints in dock

Constraints in dock

Why the division? Once a constraint is created, it is added to a particular view object in the view hierarchy. Which view gets a constraint is based on which views that constraint affects. The three edge constraints are added to the Control because they apply to both the toolbar and its superview, the view of the BNRDetailViewController. (Recall that you changed the class of this view object from UIView to UIControl at the end of Chapter 11 to enable tapping on this view to dismiss the keyboard.) The height constraint, on the other hand, is added to the toolbar because it applies only to the toolbar.

In a XIB file, a constraint is added to the appropriate view automatically. For constraints created programmatically, creating and adding are distinct steps. In the next chapter, you will see how to determine which view a constraint should be added to when you create constraints programmatically.

If you select any of these constraints in the dock, a blue line will appear on the canvas representing the constraint. (Some constraints will be harder to see than others.) Selecting the view will show you all the constraints influencing that view.

You can delete any constraint by selecting it in the dock or by selecting its representative line in the canvas and then pressing Delete. Try it out. Delete the height constraint and then select the toolbar on the canvas and use the Pin menu to add it back.

Adding more constraints

Let’s turn now to the Name label. This label’s size and position are fine right now when run on the iPad. However, you still need to constrain this view so that it will appear appropriately if it is presented in a different language or font size.

Regardless of language, font size, or screen size, you want the Name label’s size to be fixed and its position to be near the top left corner. Select the Name label in the canvas and click the Pin menu button in the constraints menu.

Select the top and left strut at the top of the pin menu. The nearest neighbor in these directions is the Name label’s container (also the view of the BNRDetailViewController). Also, check the boxes for Width and Height to fix the label at its current size in points.

Your Pin menu should look something like Figure 15.11. Note that your values are unlikely to exactly match this figure, and that is OK. You are creating a constraint based on the position of the view in your canvas. If you change your values to match Figure 15.11, then your constraints will not match the position of your views on the canvas, and you will get misplaced view warnings. You will learn about these warnings shortly, but it is best to avoid them for now.

Figure 15.11 Example constraints for the Name label

Example constraints for the Name label

Click Add 4 Constraints at the bottom of the menu.

Now consider the text field to the right of the Name label. Regardless of screen size, the text field’s width should stretch from its position to the left of the Name label to fill most of the screen.

Select the text field and open the Pin menu. At the top of the menu, select the left and right struts, and then add these two constraints. This pins the leading edge of the text field to the Name label and the trailing edge to 20 points from the container. By pinning the text field’s trailing edge to its container, you are ensuring that the text field will always stretch to fill most of the screen regardless of the screen’s size.

You now have a problem. Notice that the lines representing the constraints on the text field are orange instead of blue. This color difference means that the text field does not have enough constraints for Auto Layout to unambiguously specify its alignment rectangle.

To get more information about this problem, find and click the red icon in the dock next to Control.

Figure 15.12 Insufficient constraints for text field

Insufficient constraints for text field

According to Interface Builder, you are missing a constraint. The text field needs its Y (vertical) position constrained. You could fix this problem by opening the Pin menu and selecting the top strut. This would pin the text field some distance from its container.

There is a better design choice: align the text field with the Name label instead. You can align two or more views according to any layout attribute. Here the best choice is to align baselines. That way the text that the user enters in the text field will line up with the text in the Name label.

On the canvas, select the text field and hold the Shift key down to select the Name label at the same time. From the constraints menu, click the Insufficient constraints for text field icon to reveal the Align menu. Check the box next to Baselines and add 1 constraint.

Figure 15.13 Aligning baselines of label and text field

Aligning baselines of label and text field

Now the lines representing the constraints are blue again, and the red icon has disappeared; the text field has enough constraints to size and position it unambiguously.

A missing constraint (also called an ambiguous layout) is just one of the problems that can pop up when adding constraints. Two other types are conflicting constraints and constraints not matching the view’s size and position on the canvas. Later in the chapter, you will see how to debug these problems.

Build and run the project on iPad. Select an item, and you should see the top text field stretching to fill in the extra space provided by the iPad’s screen size (Figure 15.14).

Figure 15.14 Name field stretching

Name field stretching

Adding even more constraints

Now that you know about pin and align constraints, you can add constraints for the rest of the views, starting with the Serial label.

Here are the constraints that you need to add for the Serial label:

· top edge should be pinned at its current distance from the Name label

· leading (left) edge should be aligned with the Name label’s leading edge

· height and width should be fixed at their current values

So far, you have added constraints using the Pin and Align menus. You can also add constraints by Control-dragging on the canvas. This dragging is similar to setting up outlets and actions. You drag from one view to another. After you release the mouse button, you get a list of constraints that you can add. The list is context-sensitive; it is populated based on the direction of the drag and what views you are dragging to and from.

Let’s see how Control-dragging works. Select the Serial label in the canvas. Then Control-drag from this label to the Name label. When you let go of the mouse button, a menu will appear (Figure 15.15).

Figure 15.15 Adding constraint by Control-dragging between views

Adding constraint by Control-dragging between views

Select Vertical Spacing from the menu to fix the vertical distance between these two views at its current value. This is identical to opening the Pin menu and selecting the top strut.

Control-drag to the Name label again. This time, select Left from the menu. This is identical to opening the Align menu and checking Leading Edges.

Finally, you need to fix the label’s height and width at their current values. Because these constraints affect only the Serial label, you do not Control-drag to another view. Instead, make a very short diagonal Control-drag within the Serial label.

When the menu appears, hold down the Shift key to select both Width and Height. (If you are only seeing one of these choices, then your Control-drag was vertical or horizontal instead of diagonal. Try again.)

Now you need to constrain the text field to the right of the Serial label. This view needs three constraints:

· leading edge should be pinned at its current distance from the Serial label

· baseline should be aligned with the Serial label’s baseline

· trailing edge should be pinned at its current distance from its container

Select the text field and Control-drag to the Serial label. Let go and Shift-click Horizontal Spacing and Baseline. Then Control-drag from the text field right to the superview. Select Trailing Space to Container.

There are three more views that need constraints: the Value label, the text field to its right, and the label that displays the date. Using the constraints menu or Control-dragging, add the following constraints:

For the Value label…

· top edge should be pinned at its current distance from the Serial label

· leading edge should be aligned with the Serial label’s leading edge

· height and width should be fixed at their current values

For the text field…

· leading edge should be pinned at its current distance from the Value label

· baseline should be aligned with the Value label’s baseline

· trailing edge should be pinned at its current distance from its container

For the date label…

· top edge should be pinned at its current distance from the text field that displays the item’s value

· leading and trailing edges should be pinned at their current distances from its container

· height should be fixed at its current value

Build and run the application on iPad. The BNRDetailViewController should look good at this point.

Priorities

Each constraint has a priority level that is used to determine which constraint wins when more than one constraint conflicts. A priority is a value from 1 to 1000, where 1000 is a required constraint. By default, constraints are required, so all of the constraints that you have added are required. This means that the priority level would not help if you had conflicting constraints. Instead, Auto Layout would report an issue regarding unsatisfiable constraints. Typically, you find the constraints that conflict and then either remove one (often, the culprit is an obvious accident) or reduce the priority level of a constraint to resolve the conflict but keep all the constraints in play. You will learn more about debugging this issue in the next section.

Debugging Constraints

You have added constraints to BNRDetailViewController.xib that will allow Auto Layout to determine alignment rectangles for every view in the hierarchy and give you the layout that you want on the iPhone and the iPad. Given the sheer number of constraints, it is easy to introduce problems. You can miss a constraint, you could have constraints that conflict, or you could have a constraint that conflicts with how a view appears on the canvas.

Fortunately, there are several tools you can use to debug Auto Layout constraints. Let’s take a look at each of these in turn and see ways to fix them.

Ambiguous layout

An ambiguous layout occurs when there is more then one way to fulfill a set of constraints. Typically, this means that you are missing at least one constraint.

Currently, there are no ambiguous layouts, so let’s introduce some. Add two labels to the UIControl under the dateLabel and position them next to one another. In the attributes inspector, change the background color of the labels to light gray so that you can see their frames. The interface should look like Figure 15.16.

Figure 15.16 New labels

New labels

Now let’s add some constraints to both labels. Hold down the Shift key and select both labels. Open the Pin Auto Layout menu, select the top, left, and right struts at the top, and then click Add 5 Constraints (Figure 15.17).

Figure 15.17 Adding constraints to multiple views at once

Adding constraints to multiple views at once

You have pinned three edges for two labels, so you may be wondering “Why 5 constraints and not 6?” The answer is that the trailing edge constraint of the label on the left and the leading edge constraint of the label on the right are the same constraint. Interface Builder recognizes this and only adds one constraint to satisfy both attributes.

If you build and run on an iPhone, everything will look fine, but running on the iPad is a different story. Build and run the project on an iPad and navigate to the detail interface. One of the two labels is wider than the other (Figure 15.18).

Figure 15.18 Width of labels is surprising on iPad

Width of labels is surprising on iPad

These labels do not have enough constraints to unambiguously define their frames. Auto Layout takes its best guess at runtime, and it is not what you wanted on the iPad. You are going to use two UIView methods, hasAmbiguousLayout and exerciseAmbiguousLayout, to debug this situation.

Open BNRDetailViewController.m. Override the method viewDidLayoutSubviews to check if any of its subviews has an ambiguous layout.

- (void)viewDidLayoutSubviews

{

for (UIView *subview in self.view.subviews) {

if([subview hasAmbiguousLayout])

NSLog(@"AMBIGUOUS: %@", subview);

}

}

viewDidLayoutSubviews gets called any time the view changes in size (for example, when the device is rotated) or when it is first presented on the screen.

Build and run the application on the iPad simulator and navigate to the BNRDetailViewController. Then check the console; it will report that the two labels are ambiguous. (Note that the output in the console is often duplicated, resulting in twice as many messages as you would expect.)

You can go a step further to actually see the other way this layout might appear. In BNRDetailViewController.m, edit the backgroundTapped: method to send the message exerciseAmbiguityInLayout to any ambiguous views.

- (IBAction)backgroundTapped:(id)sender

{

[self.view endEditing:YES];

for (UIView *subview in self.view.subviews) {

if ([subview hasAmbiguousLayout]) {

[subview exerciseAmbiguityInLayout];

}

}

}

Build and run the application again. Once on the BNRDetailViewController, tap the background view anywhere. The width of the two labels will swap.

Figure 15.19 Tapping in the background demonstrates the other possible layout

Tapping in the background demonstrates the other possible layout

Neither of the widths of the labels has been constrained, and so there is more than one solution to the system of Auto Layout equations. Because of this, there is an ambiguous layout and tapping the background switches between the two possible solutions. Due to the other constraints you have specified, as long as one of the labels has its width constrained, the other label’s width can be determined. You will get rid of the ambiguous layout by giving the two labels equal widths.

In BNRDetailViewController.xib, Control-drag from one label to the other, and then select Equal Widths. Build and run the application on iPad. Your labels have the same width. Check the console to confirm that there are no more ambiguous layouts. Tapping on the background will have no effect on the interface.

Your interface is once again properly set up. All views have enough constraints to fully construct their alignment rectangle, and so there are no more ambiguous views.

In BNRDetailViewController.xib, select and delete the two test labels.

The exerciseAmbiguityInLayout method is purely a debugging tool that allows Auto Layout to show you where your layouts could potentially end up. You should never leave this code in an application that you are shipping.

In BNRDetailViewController.m, delete viewDidLayoutSubviews and delete the code that calls exerciseAmbiguityInLayout in backgroundTapped:.

- (void)viewDidLayoutSubviews

{

for (UIView *subview in self.view.subviews) {

if([subview hasAmbiguousLayout])

NSLog(@"AMBIGUOUS: %@", subview);

}

}

- (IBAction)backgroundTapped:(id)sender

{

[self.view endEditing:YES];

for (UIView *subview in self.view.subviews) {

if ([subview hasAmbiguousLayout]) {

[subview exerciseAmbiguityInLayout];

}

}

}

Unsatisfiable constraints

The problem of unsatisfiable constraints occurs when two or more constraints conflict. This often means that a view has too many constraints. To illustrate, let’s introduce this problem to the BNRDetailViewController.

In BNRDetailViewController.xib, select the label that displays the date. In the attributes inspector, change its background to light gray so that you can see its frame in the layout. Next, pin the width of this label to its current value.

Just as before, if you were to build and run the application on an iPhone, everything would be fine. Build and run the application on an iPad. The label may look just as it did before, but take a look at the console.

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:0xa333da0 H:[UILabel:0xa333ca0(280)]>",

"<NSLayoutConstraint:0xa394500 H:[UILabel:0xa333ca0]-(20)-|

(Names: '|':UIControl:0xa38cd80 )>",

"<NSLayoutConstraint:0xa394530 H:|-(20)-[UILabel:0xa333ca0]

(Names: '|':UIControl:0xa38cd80 )>",

"<NSAutoresizingMaskLayoutConstraint:0xa3a1a70 h=-&-

v=-&- UIControl:0xa38cd80.width == _UIParallaxDimmingView:0xa37b140.width>",

"<NSAutoresizingMaskLayoutConstraint:0xa3a21d0 h=--&

v=--& H:[_UIParallaxDimmingView:0xa37b140(768)]>"

)

Will attempt to recover by breaking constraint

<NSLayoutConstraint:0xa333da0 H:[UILabel:0xa333ca0(280)]>

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.

First, Xcode informs you that it is “unable to simultaneously satisfy constraints” and gives you some hints on what to look for. The console then lists all of the constraints that are related to the issue. Finally, you are told that one of the constraints will be ignored so that the label will have a valid frame. In this case, the constraint to pin the width will be ignored.

You can also just think through the problem. You constrained this label’s leading and trailing edges to resize with the superview. Then you constrained its width to a fixed value. These are conflicting constraints, and the solution is to remove one.

In BNRDetailViewController.xib, delete the width constraint that you just added to the label and set its background color back to clear.

Misplaced views

If a view’s frame in a XIB does not match its constraints, then you have a misplaced view problem. This means that the frame of that view at runtime will not match how it currently appears on the canvas. Let’s cause a misplaced view problem.

Select the label that displays the date and drag it down a little bit so that the interface looks like Figure 15.20.

Figure 15.20 A misplaced view

A misplaced view

A rectangle with an orange, dashed border will appear where the label used to be. This is the runtime frame; at runtime, the existing constraints will position the label where this rectangle is and not where you just dragged it to.

How you fix the problem depends on whether the view’s size and position on the canvas are what you want. If so, then you change the constraints to work with this new position. If not, then you change the view’s size or position to match the constraints. Let’s say that moving the label was an accident and that you want the view’s position to match the existing constraints.

Select the date label. Then, in the Auto Layout constraints menu, select the A misplaced view icon to reveal the Resolve Auto Layout Issues menu (Figure 15.21).

Figure 15.21 Resolve Auto Layout Issues menu

Resolve Auto Layout Issues menu

Select Update Frames at the top. This will reposition the label to match its constraints.

On the other hand, if you wanted the constraints to change to match the new position of the view, you would choose to Update Constraints.

The Resolve Auto Layout Issues menu is very powerful. Here is a description of the items in the top half of the menu.

Update Frames

Adjusts the frame of the view to match its constraints.

Update Constraints

Adjusts the constraints of the view to match its frame.

Add Missing Constraints

For views with an ambiguous layout, this will add the necessary constraints to remove the ambiguity. However, the new constraints might not be what you want, so make sure to double-check and test this resolution.

Reset to Suggested Constraints

This will remove any existing constraints from the view and add new constraints. These suggested constraints are sensitive to the context of the view. For example, if the view is near the top of its superview, the suggested constraints will probably pin it to the top, whereas if the view is near the bottom of its superview, it will probably be pinned to the bottom.

Clear Constraints

All constraints are removed. If no explicit constraints are added to this view, it will have the default fixed position and size constraints added to it.

The bottom section repeats these options but applies them to all of the subviews instead of only the selected view(s).

Bronze Challenge: Practice Makes Perfect

Open the Resolve Auto Layout Issues menu and select Clear All Constraints in Control. Review Figure 15.4 or run the application on the iPad simulator and navigate to the detail interface to remind yourself of the initial problems. Add the constraints back on your own to achieve a reasonable-looking detail interface on the iPad.

Play with different ways of adding constraints (menus vs. Control-dragging), adding multiple constraints at once, and constraining multiple views at once. Use the debugging tools and the warnings and errors that Interface Builder provides to ensure that your constraints are sufficient.

Silver Challenge: Universalize Quiz

Make Quiz a universal application. If you run the universalized app on the iPad simulator without adding any constraints, the interface will look like this:

Figure 15.22 Universalized Quiz running on the iPad

Universalized Quiz running on the iPad

Decide how the interface should look on the iPad and then add constraints in BNRQuizViewController.xib to ensure that it will appear as you want on any device.

For the More Curious: Debugging Using the Auto Layout Trace

In this chapter, to check for an ambiguous layout, you iterated over the subviews of BNRDetailViewController’s view, and then asked each subview if it hasAmbiguousLayout. This worked well since all of the views you were working with were subviews of the main view. What if your view hierarchy was much more complex? There is another way to find ambiguous layouts using some private methods that Apple has made known.

UIWindow has a private instance method named _autolayoutTrace. This will return a string with a graphical representation of that window’s entire view hierarchy, and will tag views with an ambiguous layout with AMBIGUOUS LAYOUT.

The best way to use this is to place a breakpoint somewhere in your code that will get triggered after the affected view is on screen. Once this breakpoint has been hit, type the following into the debugger, and press Enter.

(lldb) po [[UIWindow keyWindow] _autolayoutTrace]

This can be very helpful when your UI does not look as you expect but you are not sure where the problem is originating from.

For the More Curious: Multiple XIB Files

It is possible that you may have a view controller that needs completely different views depending on the type of device that the application is running on. If this is the case, you can create two XIB files – one for each device type.

To get a view controller to load the appropriate XIB file for each device, you simply add a suffix to the filename:

BNRDetailViewController~iphone.xib

BNRDetailViewController~ipad.xib

By naming XIB files in this way, a view controller will automatically find and load the right file at runtime.

Note that using distinct XIB files is not an alternative to using Auto Layout. You will still need to add constraints to both files. Auto Layout also enables your interface to respond appropriately to other differences, like in the user’s language or preferred font size or in the orientation of the device.