Chapter     3

Layout Recipes

The user interface and its layout are essential to most applications, especially in iOS with its orientation-sensitive devices. Users expect apps to support both portrait and landscape orientation in most cases. They also expect apps to run on both the iPhone and iPad. In other words, they expect your apps to have dynamic layouts.

This chapter shows you how to use Auto Layout, a great way to build dynamic user interfaces in iOS 7. Auto Layout is a feature first introduced in Xcode 4, and it provides a way to handle view layouts in iOS apps via the interface builder as well as programmatically. Auto Layout offers a very powerful model with which you can build layouts that scale and adapt to screen rotations in a way that was not possible without reverting to complicated code. Auto Layout also makes tasks such as supporting multiple languages, which might read right-to-left, a breeze.

Xcode 5 introduces many capabilities that make the process of creating layouts more flexible and less error prone. The developer now has more control over layouts; however, with more control comes more ways to create errors.

In this chapter, you will learn how to create flexible interfaces using Auto Layout. We will cover creating these interfaces using the interface builder and also using the programmatic method. Lastly, we will cover the types of errors incurred and how to troubleshoot them.

Recipe 3-1: Using Auto Layout

In this recipe, we show you how Auto Layout works. You will learn how to add constraints, which are relationships between views, in various ways. You will also learn how to disable auto constraints as well as how to take advantage of the preview tool. The preview tool allows you to render your layout in real time without simulating.

Auto Layout Constraints

Auto Layout, in its essence, consists of “constraints” dictating the relationship between user interface elements as well as a layout engine that enforces the constraints. A constraint is a rule that dictates how elements should behave when other elements near them, such as the containing view, change. By this definition, the layout engine enforces these rules.

In Chapter 1, we used a handy Xcode 5 feature to set the suggested constraints on some buttons. Auto Layout, which is turned on by default, allows you to set up constraints to make your application look good in various sizes and orientations. To see how Auto Layout works, you will create an application with a simple user interface that automatically adapts to both portrait and landscape orientations.

Start by creating a new single view application and then build a user interface in the provided view controller, such as the one in Figure 3-1,using labels, a text field, and a text view. To make the boundary lines more clear, give your view controller a gray background color. You can do this by selecting your view controller in the storyboard and changing the background property in the attributes inspector.

9781430259596_Fig03-01.jpg

Figure 3-1. The user interface in portrait orientation

When you position and size the elements, be sure to use the default spacing between those elements and the main view’s boundaries, as well as between those elements and the subviews; in other words, where Interface Builder snaps during dragging, as shown in Figure 3-2.

9781430259596_Fig03-02.jpg

Figure 3-2. The dashed lines in Interface Builder indicate default spacing

Unlike Xcode 4, Xcode 5 does not add constraints to your layout until you are ready for them. Instead, if you have not specified any constraints, Xcode will add fixed position and size constraints at build time. This will ensure your interface looks exactly as it does in the Interface Builder window. This is handy for when you are prototyping and you don’t really care what it does in a different screen resolution or rotation.

When you are ready to add constraints, there are several ways to do so. We’ll demonstrate three different approaches in the following sections.

Adding Constraints Using the Control-Click and Drag Method

Starting with the text field, control-click and drag from the left side of the text field to the view controller and choose “Leading Space to Container” from the popup, as shown in Figure 3-3 and Figure 3-4.

9781430259596_Fig03-03.jpg

Figure 3-3. Control-click and drag from the text field to the view controller

9781430259596_Fig03-04.jpg

Figure 3-4. Choosing “Leading Space to Container” from the popup

Now you should see that the constraint was successfully added to the text field, as indicated by the orange line that appears (Figure 3-5). The line is orange instead of blue because we have a problem, as indicated in the status window by the warning icon. Without getting into too much detail, this orange line is telling us we don’t have enough constraints.

9781430259596_Fig03-05.jpg

Figure 3-5. Choosing “Leading Space to Container” from the popup

Note   Orange lines are an addition in Xcode 5; previously, constraints were added for you. Because you now have more flexibility, you can create these types of errors. We’ll cover errors in more depth in Recipe 3-3.

If you control-click and drag from one object to another, say between the label and the text field, you’ll see that the popup will have different options, as shown in Figure 3-6.

9781430259596_Fig03-06.jpg

Figure 3-6. Options shown when control-clicking between text field and label

Now, we could go through and control-click from each item and add constraints manually, or we could use some more new features in Xcode 5 to speed up the process.

Adding Constraints Using the Auto Layout Issue-Resolving Menu

Apple has added a new menu to Xcode 5 that helps us add constraints quickly. By using the Auto Layout issue-resolving menu, we have access to a number of tools that can suggest constraints for us. Select all the objects and choose the “Reset to Suggested Constraints” option from the issue-resolving menu, as shown in Figure 3-7.

9781430259596_Fig03-07.jpg

Figure 3-7. Resetting to suggested constraints from the issue-resolving menu

The view controller should now have a number of new constraints, as shown in Figure 3-8. You will also see that there are no more issues showing up in the status bar. Notice that none of these objects have specified height and width constraints. This is because of a nifty little feature called intrinsic content size. Intrinsic content size will determine the height and width based on the content contained within it. Normally, you would not want to add explicit width or height constraints because if the size of the content changes, you might have clipped text or other bizarre results.

9781430259596_Fig03-08.jpg

Figure 3-8. View controller with added constraints from the issue-resolving menu

Now that the issues have all been cleared, you can build and run the application to see how it is affected in both portrait and landscape modes (Figure 3-9). To change the simulator to landscape view, select hardware arrow.jpg rotate right from the simulator file menu.

9781430259596_Fig03-09.jpg

Figure 3-9. Simulated app in landscape view taking advantage of Auto Layout

You now know how to use suggested constraints as a starting point, and you also know how to add constraints using the control-click and drag method. There is one more way to add and manipulate constraints, which is by using the Pin menu.

Adding Constraints Using the Pin Menu

Because we already have all the constraints we need on our scene, we’ll want to delete some before we can demonstrate the Pin menu. Select the text view and choose “clear constraints” from the issue-resolving menu.

Note   You can delete constraints individually by selecting the constraint line and pressing the “delete” key.

With all the constraints removed from the text view, select the text view (if it is not already selected) and press the “Pin” button to bring up its menu, as shown in Figure 3-10. Fill in the values for the top, right, left, and bottom distance to the nearest neighbor. If there is no neighbor, then the distance to the nearest neighbor becomes the distance to the edge of the superview. That is, if a label does not have anything between it and the edge of the screen (superview), then the closest neighbor becomes the edge of the screen.

9781430259596_Fig03-10.jpg

Figure 3-10. Adding missing constraints to the text view using the Pin menu

Notice that there are suggested values already provided. Despite this, you actually have to type them in to take effect. So type the following values and click “Add constraints:”

  • Top: 13
  • Left: 20
  • Right: 20
  • Bottom: 20

Now select the text view again and take a look at the constraints that were added. You should now see four new constraints, as shown in Figure 3-11.

9781430259596_Fig03-11.jpg

Figure 3-11. Constraints added from the Pin menu

Constraint Priorities

These new Auto Layout changes in Xcode 5 are a huge time saver compared with the Xcode 4 version of Auto Layout. However, what if you wanted a slightly different layout behavior for the Name text field, for example? What if you didn’t want it to grow beyond a certain width when switching between device orientations? (It’s a common principle in human computer interaction that the size of input fields should reflect the size of expected content). Let’s say you want the Name text field to grow when the screen rotates, but only up to 350 pixels. This is where Auto Layout starts to shine.

Translated into the language of constraints, you want to add a constraint that states that the text field’s width must be no more than 350 pixels. Adding this constraint yields a logical problem, though. When the screen rotates, the system can’t satisfy both the constraints. The constraint that pins the right edge of the text field conflicts with the one that dictates its maximum width.

What can you do about that? One idea is to remove the constraint inserted by Interface Builder. However, it doesn’t take much thought to realize that it would leave you with a constraint that’s true for many values, but not decisive enough to settle for one value. No, you need both constraints: you want the right edge of the text field to pin to the right edge of the screen unless that makes the width larger than 350 pixels. The solution is constraint priorities.

Auto Layout offers the possibility to set a priority value between 0 and 1000 on individual constraints. A value of 1000 means the constraint is required, but for any other value the constraint with the higher priority takes precedence. In this case, it means you can make the width constraint required but set a lower priority for the “pin to the right edge” constraint. Then, when the screen rotates, the width constraint will “win” over the other and you will get the effect you seek.

Start by adding the width constraint. Select the text field and click the “Pin” button in the Auto Layout bar located in the lower-right corner of Interface Builder. Then select the width constraint, as shown in Figure 3-12. For now, you can leave the width value the same. Click the “Add Constraints” button.

9781430259596_Fig03-12.jpg

Figure 3-12. The Auto Layout bar in Interface Builder allows you to add your own constraints

Now select the newly added width constraint by clicking it. In the attributes inspector for the new width constraint, set the relation to “Less Than or Equal” and the constant to “350.” Leave the priority at 1,000 (required), as shown in Figure 3-13.

9781430259596_Fig03-13.jpg

Figure 3-13. Setting a width constraint to “Less Than or Equal” to “350”

What’s left to do now is to lower the priority of the “trailing space to superview” constraint. Make sure the text field is selected and then select the right constraint, the one between the edge of the text field and the view controller edge. Change the value of the property to “500,” as shown in Figure 3-14.

9781430259596_Fig03-14.jpg

Figure 3-14. Lowering the priority of a constraint to 500

If you have done this last step correctly, your new constraints should resemble Figure 3-15. A circle indicating a “less than or equal” size is on the width constraint, and the lower priority constraint is now dashed.

9781430259596_Fig03-15.jpg

Figure 3-15. Constraints indicating a “less than or equal” size, with a lower priority right-hand constraint

If you build and run your application now, you will see that when you rotate the device, the text field grows but stays at the maximum width of 350, as shown in Figure 3-16.

9781430259596_Fig03-16.jpg

Figure 3-16. A user interface with a text field that has a width constraint of 350 pixels

Adding a Trailing Button

Let’s make things a little more complicated. What if you want to add a button on the right side of the text field while still keeping the current width constraint? You can accomplish this using Auto Layout. In this section, we’ll create a trailing button, which is a button that will stay pinned to the trailing (in this case, right) edge of the text field.

Before jumping in and adding constraints, it’s a good idea to stop and think about the layout in terms of constraints. You should do the following:

  1. Allow the width of the text field to be less than or equal to 350.
  2. Pin the trailing edge of the text field to the leading edge of the button.
  3. Pin the trailing edge of the button to the trailing edge of the screen, unless the first constraint is violated.

Note   The reason we’re using the terms leading and trailing instead of left and right (which are also valid attributes) is that trailing and leading are defined as adapting to changes of text directions in, for example, the Hebrew language, which is read right to left. In such an instance, leading becomes right and trailing becomes left, and the user interface adapts accordingly. This is yet another reason to use Auto Layout.

9781430259596_Fig03-17.jpg

Figure 3-17. Leading and trailing edges in a left-to-right language locale

Before you add the button to the user interface, delete the trailing edge constraint on the text field and resize the text field to a width of 199 points. You can easily change this value in the size inspector found in the utilities pane. For the time being, don’t worry about the issue warning you get by removing this constraint.

Now add a new button to the right of the text field and snap it to the default snap line on the right. You could add the constraints using the “reset to suggested” constraints and it will work fine, but for the sake of better understanding, use the control-click method using the following steps:

  1. Control-click and drag from the text field to the button and choose “Center Y” from the pop up.
  2. Control-click and drag from the button to the edge of the view controller and choose “Trailing Space to Container.”
  3. Control-click and drag from the trailing edge of the text field to the leading edge of the button and choose “Horizontal Spacing.”
  4. Select the button and choose “add a width and height” from the Pin menu. You don’t need to put in values; just use the ones that are there.
  5. Select the constraint that is created between the trailing edge of the button and the right-side container and change the priority to 500.

If you have done everything correctly, your constraints should resemble Figure 3-18.

9781430259596_Fig03-18.jpg

Figure 3-18. Correct trailing-edge button constraints

Note   Although you typically should not add fixed widths to any objects that can have variable content size, we had to add fixed widths in this example. This is because without a fixed width on either the text field or the button, Auto Layout would not know which one to stretch.

Once all the constraints are in place, build and run your application. When rotated, the user interface should adapt nicely and keep the text field at the maximum width while the button stays pinned to its right, as shown in Figure 3-19.

9781430259596_Fig03-19.jpg

Figure 3-19. A user interface with a text field that has a width constraint of 350 pixels and a button trailing to its right

Disabling Auto Layout

In contrast, let’s look at how the app behaves with Auto Layout turned off. Select the entire view controller and go to the file inspector, located in Xcode’s right Utilities View panel. In the Interface Builder Document section (see Figure 3-20), deselect “Use Auto Layout.”

9781430259596_Fig03-20.jpg

Figure 3-20. Auto Layout is turned on by default for new projects

Note   It is not normally recommended to disable Auto Layout and then re-enable it, as it will wipe out all your constraints.

Now build and run the app again. As you can see in Figure 3-21, rotating the device results in a significantly worse user experience.

9781430259596_Fig03-21.jpg

Figure 3-21. A landscape orientation user interface without Auto Layout enabled

Note   This behavior is exactly what you would see if you never added any constraints to begin with.

Using the Preview Tool

A new handy feature added to Xcode 5 is the preview tool, which will allow you to preview Auto Layout in other orientations or devices without having to simulate. You can get to the preview tool by clicking the “Related Files” button, navigating to “Preview,” and then clicking “Main.storyboard” while simultaneously pressing the “Option” and “Shift” keys, as shown in Figure 3-22.

9781430259596_Fig03-22.jpg

Figure 3-22. Getting to the preview tool

Next, you’ll see a popup dialog box. Click the “+” button, which is shown highlighted in Figure 3-23, and press “Enter.” This will split your storyboard editor window with a preview window.

9781430259596_Fig03-23.jpg

Figure 3-23. Getting to the preview tool

You can select the orientation and device from the lower-right corner of the new preview window, as shown in Figure 3-24. You’ll notice that the constraints have been re-added by us.

9781430259596_Fig03-24.jpg

Figure 3-24. Storyboard editor window with a landscape layout preview on the right

Although simple, we hope that the preceding example has opened your eyes to the possibilities of Auto Layout. The next recipe takes it to the next level, where you’ll create constraints from code, thus constructing a truly dynamic layout.

Recipe 3-2: Programming Auto Layout

The preferred way to set up Auto Layout constraints is to use Interface Builder, as shown in Recipe 3.2. The main reason is that Interface Builder makes it easier to diagnose bad constraints, plus it’s much faster.

However, there are situations in which you can’t define your Auto Layout constraints in Interface Builder, such as if you create the user interface components dynamically in code. For these situations, you need to revert to code for setting up the constraints. In this recipe, we’ll show you how.

Setting Up the Application

For this recipe, you will build a simple app that has three buttons: one for adding a new image view to the screen, one for removing the last image view added, and one for removing all added image views. You will use Auto Layout to position the buttons in a row at the top of your screen. You also will use Auto Layout to position the image views so they overlap each other, with the last-added image view on top and with each image view having a 10 percent increase in size compared to the previous.

Start by creating a new single view application and then add the following properties to its view controller, as shown in Listing 3-1.

Listing 3-1.  Adding interface properties to ViewController.h

//
//  ViewController.h
//  Recipe 2.2 Coding Auto Layout
//

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property (strong, nonatomic) UIButton *addButton;
@property (strong, nonatomic) UIButton *removeButton;
@property (strong, nonatomic) UIButton *clearButton;
@property (strong, nonatomic) NSMutableArray *imageViews;
@property (strong, nonatomic) NSMutableArray *imageViewConstraints;

@end

Create the three buttons directly in code using a helper method to reduce code duplication. Switch to ViewController.m and add the code shown in Listing 3-2.

Listing 3-2.  Adding a helper method to create three buttons in the ViewController.m file

- (UIButton *)addButtonWithTitle:(NSString *)title action:(SEL)selector
{
    UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
    [button setTitle:title forState:UIControlStateNormal];
    [button addTarget:self action:selector forControlEvents:UIControlEventTouchUpInside];
    button.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:button];
    return button;
}

Listing 3-2 creates a new button with the provided title and action method. What’s notable here is the setting of the translatesAutoresizingMaskIntoConstraints property of the button to NO. This is important to do if you’re defining your own Auto Layout constraints; otherwise, you’re likely to end up with conflicting constraints. Recipe 3-3 has more to say on this subject.

Note   You don’t explicitly set view frames when using Auto Layout. Instead, a view’s position and size are dictated by the constraints you define.

With the helper method in place, you can turn to the viewDidLoad method and add code to create the buttons, as shown in Listing 3-3.

Listing 3-3.  Calling the helper method to create the three buttons

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.addButton = [self addButtonWithTitle:@"Add" action:@selector(addImageView)];
    self.removeButton = [self addButtonWithTitle:@"Remove" action:@selector(removeImageView)];
    self.clearButton = [self addButtonWithTitle:@"Clear" action:@selector(clearImageViews)];
}

Next, add stubs, as shown in Listing 3-4, for the three action methods for each of the three buttons. You’ll implement these methods later, but leave them empty for now.

Listing 3-4.  Adding placeholder stubs for the button actions

- (void)addImageView
{
}

- (void)removeImageView
{
}

- (void)clearImageViews
{
}

If you run your application now, you would see nothing but a white screen. The buttons wouldn’t show because you haven’t defined any Auto Layout constraints to dictate their positions and sizes. So let’s go ahead and do that.

There are two principal ways to create constraints from code. You can either use Visual Format Language, a visually descriptive syntax for defining Auto Layout constraints, or you can use the constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant: method. The former has the advantage in that it provides better visualization of the constraints created; the latter, on the other hand, provides completeness (not all constraints can be expressed using Visual Format Language).

Often you’ll mix the two ways to create your constraints. In this case, use the format language for positioning the buttons and a mix of the two for placing the image views.

Visual Format Language

Before moving on and creating the constraints, let’s take a quick look at Visual Format Language. For example, this string defines constraints that position button2 right next to button1 with a spacing of 20 pixels between the two:

[button1]-20-[button2]

A single hyphen indicates default spacing:

[button1]-[button2]

Here are same constraints but for vertical layout:

V:[button1]-[button2]

Although horizontal is the default, you can explicitly state it:

H:[button1]-[button2]

The spacing toward the superview is indicated with a | character. The following example states that textField should be pinned to both leading and trailing ends of the superview, with default spacing:

|-[textField]-|

You can also define a component size. This example specifies that button1 is 50 pixels wide and button2 has the same width as button1:

[button1(50)]-[button2(==button1)]

You also can have inequalities, as in the following example, which specifies that button1 is at least 50 pixels wide:

[button1(>=50)]

You can set both a minimum and a maximum width at the same time:

[button1(>=50, <=100)]

You also can set priorities on the size constraints. For example, button1 is at least 50 pixels wide, but with a priority at 500, which makes it non-required but desirable:

[button1(>=50@500)

Table 3-1 shows the syntax elements and some additional examples of Visual Format Language.

Table 3-1. Visual Format Language Syntax Elements

Syntax Elements

Examples

Description

H:, V: H:|-[statusLabel]-| V:|[textView]| Horizontal or vertial orientation. Default orientation is horizontal, so the H: can therefore be omitted.
| |[textView]| Indicates the superview; its leading end if on the left side and trailing on the right
- [button1]-[button2] Standard space
-N- |-20-[view] An N-sized spacing
[view] Indicates a subview
==, >=, <= [view1(==view2)] [view(>=30, <=100)] Relation operators. Can only be used in size constraints
@N [view(==50@500)] [view1(==view2@500, >=30)] Constraint priority. Can only be used in size constraints. Default priority is 1000 (i.e., a required constraint)

Now, let’s add constraints that position the three buttons in a row at the top of the screen. Because these constraints are always the same, you add them directly in the viewDidLoad method. Start by creating a dictionary that contains the buttons, with identifying keys, as shown in Listing 3-5. Auto Layout uses the dictionary to map identifiers in the format language strings to the corresponding views (buttons in this case).

Listing 3-5.  Creating a dictionary for buttons and their identifying keys

NSDictionary *viewsDictionary =
[[NSDictionary alloc] initWithObjectsAndKeys:
self.addButton, @"addButton",
self.removeButton, @"removeButton",
self.clearButton, @"clearButton", nil];

Then you add the constraints that pin the buttons to each other in a row. Do this by calling the addConstraints:constraintsWithVisualFormat:options:metrics:views: method of the main view, providing the visual format string (marked in bold), as shown in Listing 3-6.

Listing 3-6.  Adding constraints to pin buttons to each other

[self.view addConstraints:[NSLayoutConstraint
    constraintsWithVisualFormat:@"H:|-[addButton]-[removeButton]-[clearButton]"
    options:0 metrics:nil views:viewsDictionary]];

Next, pin the buttons to the top of the screen, as shown in Listing 3-7.

Listing 3-7.  Pinning buttons to the top of the screen

[self.view addConstraints:[NSLayoutConstraint
    constraintsWithVisualFormat:@"V:|-[addButton]"
    options:0 metrics:nil views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint
    constraintsWithVisualFormat:@"V:|-[removeButton]"
    options:0 metrics:nil views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint
    constraintsWithVisualFormat:@"V:|-[clearButton]"
    options:0 metrics:nil views:viewsDictionary]];

The viewDidLoad method should look like Listing 3-8 at this point.

Listing 3-8.  The viewDidLoad method with Listings 3-5 to 3-7 added to it

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.addButton = [self addButtonWithTitle:@"Add" action:@selector(addImageView)];
    self.removeButton = [self addButtonWithTitle:@"Remove" action:@selector(removeImageView)];
    self.clearButton = [self addButtonWithTitle:@"Clear" action:@selector(clearImageViews)];

    NSDictionary *viewsDictionary =
[[NSDictionary alloc] initWithObjectsAndKeys:
self.addButton, @"addButton",
self.removeButton, @"removeButton",
self.clearButton, @"clearButton", nil];

    [self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"H:|-[addButton]-[removeButton]-[clearButton]"
options:0 metrics:nil views:viewsDictionary]];

    [self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:|-[addButton]"
options:0 metrics:nil views:viewsDictionary]];
    [self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:|-[removeButton]"
options:0 metrics:nil views:viewsDictionary]];
    [self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:|-[clearButton]"
options:0 metrics:nil views:viewsDictionary]];
}

You now can build and run your application. Your screen should look like the one in Figure 3-25.

9781430259596_Fig03-25.jpg

Figure 3-25. A row of buttons positioned using Auto Layout

Now that you’ve verified that the layout is as expected, it’s time to implement the respective button’s action method. We’ll start with the adding of image views.

Adding Image Views

Before you move on and start implementing the addImageView action method, you need to do a little more setup and initialization. First, you need an image that you’re going to populate the image views with. (For simplicity, we use the same image for all image views.) Using a finder window, drag an image of your liking, which is in the PNG format, to the Supporting Files folder of your project. (Chapter 1 contains a detailed description about how to add resource files, such as images, to an Xcode project.) In the following code, be sure to replace the name “sweflag,” which is the name of the image we chose, to the name of your image file.

Next, initialize the two array properties that you added to the view controller’s header file in the beginning of this recipe. Add the code in Listing 3-9 to the viewDidLoad method. The code for creating the three buttons and their constraints has been removed for brevity.

Listing 3-9.  Initializing the two array properties for holding image views and their constraints

- (void)viewDidLoad
{
    [super viewDidLoad];

    // ...

    self.imageViews = [[NSMutableArray alloc]initWithCapacity:10];
    self.imageViewConstraints = [[NSMutableArray alloc]initWithCapacity:10];
}

Now implement the addImageView action method, as shown in Listing 3-10.

Listing 3-10.  Implementing the addImageView action

- (void)addImageView
{
    UIImage *image = [UIImage imageNamed:@"sweflag"];
    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    [self.view addSubview:imageView];
    imageView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.imageViews addObject:imageView];

    [self rebuildImageViewsConstraints];
}

The implementation in Listing 3-10 is straightforward. It creates and adds an image view to the main view, sets its translatesAutoresizingMaskIntoConstraints property to NO so as not to conflict with the custom constraints you’ll set up in a minute, adds itself to the imageViews array for later reference, and finally orders a rebuild of all the image views’ constraints.

Defining the Image Views’ Constraints

The rebuildImageViewsConstraints helper method removes all previous image view constraints and recreates them. The basic structure, without the actual creating of the constraints, is shown in Listing 3-11.

Listing 3-11.  Starting point for the rebuildImageViewsConstraints method

- (void)rebuildImageViewsConstraints
{
    [self.view removeConstraints:self.imageViewConstraints];
    [self.imageViewConstraints removeAllObjects];

    if (self.imageViews.count == 0)
        return;

    // TODO: Build the imageViewConstraints array

    [self.view addConstraints:self.imageViewConstraints];
}

To build the imageViewConstraints array (beginning at the code comment shown in Listing 3-11), start with the constraints that pin the first image view to the left side of the screen and immediately below the row of buttons. At the same time, set the first image view’s size to 50x50 points. Listing 3-12 shows this code.

Listing 3-12.  Building the imageViewConstraints array

UIImageView *firstImageView = [self.imageViews objectAtIndex:0];

NSDictionary *viewsDictionary = [[NSDictionary alloc]
    initWithObjectsAndKeys:self.addButton, @"firstButton", firstImageView, @"firstImageView", nil];

// Pin first view to the top left corner
[self.imageViewConstraints addObjectsFromArray:[NSLayoutConstraint
    constraintsWithVisualFormat:@"H:|-[firstImageView(50)]"
    options:0 metrics:nil views:viewsDictionary]];

[self.imageViewConstraints addObjectsFromArray:[NSLayoutConstraint
    constraintsWithVisualFormat:@"V:[firstButton]-[firstImageView(50)]"
    options:0 metrics:nil views:viewsDictionary]];

Each of the remaining image views (if any) overlap the previous one with an offset of 10 pixels to the right and down. Additionally, for effect, each image is 10 percent bigger than the previous. In pseudo code, what you should do is something like this:

imageView(N).X = imageView(N-1).X + 10;
imageView(N).Y = imagView(N-1).Y + 10;
imageView(N).Width = imageView(N-1).Width * 1.1;
imageView(N).Height = imageView(N-1).Height * 1.1;

Unfortunately, you cannot translate to Auto Layout constraints using the visual format notation. Instead, you must create the corresponding constraints explicitly, as shown in Listing 3-13.

Listing 3-13.  Creating constraints explicitly for overlapping and offsetting images

if (self.imageViews.count > 1)
{
    UIImageView *previousImageView = firstImageView;

    for (int i=1; i < self.imageViews.count; i++)
    {
        UIImageView *imageView = [self.imageViews objectAtIndex:i];

        [self.imageViewConstraints addObject:[NSLayoutConstraint
             constraintWithItem:imageView attribute:NSLayoutAttributeLeading
             relatedBy:NSLayoutRelationEqual
             toItem:previousImageView attribute:NSLayoutAttributeLeading
             multiplier:1 constant:10]];

        [self.imageViewConstraints addObject:[NSLayoutConstraint
             constraintWithItem:imageView attribute:NSLayoutAttributeTop
             relatedBy:NSLayoutRelationEqual
             toItem:previousImageView attribute:NSLayoutAttributeTop
             multiplier:1 constant:10]];

        [self.imageViewConstraints addObject:[NSLayoutConstraint
            constraintWithItem:imageView attribute:NSLayoutAttributeWidth
            relatedBy:NSLayoutRelationEqual
            toItem:previousImageView attribute:NSLayoutAttributeWidth
            multiplier:1.1 constant:0]];

        [self.imageViewConstraints addObject:[NSLayoutConstraint
            constraintWithItem:imageView attribute:NSLayoutAttributeHeight
            relatedBy:NSLayoutRelationEqual
            toItem:previousImageView attribute:NSLayoutAttributeHeight
            multiplier:1.1 constant:0]];

        previousImageView = imageView;
    }
}

Note   The Visual Format Language of Auto Layout was designed for readability over completeness. Therefore, you cannot use it to express special cases such as overlaps and multiplied-property references. For these cases, you have to create the constraints explicitly using the constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant: method.

The complete rebuildImageViewsConstraints method, once complete, should look like Listing 3-14.

Listing 3-14.  The complete rebuildImageViewsConstraints method

- (void)rebuildImageViewsConstraints
{
    [self.view removeConstraints:self.imageViewConstraints];
    [self.imageViewConstraints removeAllObjects];

    if (self.imageViews.count == 0)
        return;

    UIImageView *firstImageView = [self.imageViews objectAtIndex:0];

    // Pin first view to the top left corner
    NSDictionary *viewsDictionary =
        [[NSDictionary alloc] initWithObjectsAndKeys:
            self.addButton, @"firstButton",
            firstImageView, @"firstImageView", nil];

    [self.imageViewConstraints addObjectsFromArray:[NSLayoutConstraint
        constraintsWithVisualFormat:@"H:|-[firstImageView(50)]"
        options:0 metrics:nil views:viewsDictionary]];

    [self.imageViewConstraints addObjectsFromArray:[NSLayoutConstraint
        constraintsWithVisualFormat:@"V:[firstButton]-[firstImageView(50)]"
        options:0 metrics:nil views:viewsDictionary]];

    if (self.imageViews.count > 1)
    {
        UIImageView *previousImageView = firstImageView;

        for (int i=1; i < self.imageViews.count; i++)
        {
            UIImageView *imageView = [self.imageViews objectAtIndex:i];

            [self.imageViewConstraints addObject:[NSLayoutConstraint
                constraintWithItem:imageView attribute:NSLayoutAttributeLeading
                relatedBy:NSLayoutRelationEqual
                toItem:previousImageView attribute:NSLayoutAttributeLeading
                multiplier:1 constant:10]];

            [self.imageViewConstraints addObject:[NSLayoutConstraint
                constraintWithItem:imageView attribute:NSLayoutAttributeTop
                relatedBy:NSLayoutRelationEqual
                toItem:previousImageView attribute:NSLayoutAttributeTop
                multiplier:1 constant:10]];

            [self.imageViewConstraints addObject:[NSLayoutConstraint
                constraintWithItem:imageView attribute:NSLayoutAttributeWidth
                relatedBy:NSLayoutRelationEqual
                toItem:previousImageView attribute:NSLayoutAttributeWidth
                multiplier:1.1 constant:0]];

            [self.imageViewConstraints addObject:[NSLayoutConstraint
                constraintWithItem:imageView attribute:NSLayoutAttributeHeight
                relatedBy:NSLayoutRelationEqual
                toItem:previousImageView attribute:NSLayoutAttributeHeight
                multiplier:1.1 constant:0]];

            previousImageView = imageView;
        }

    }

    [self.view addConstraints:self.imageViewConstraints];
}

The only task remaining now is to implement the action methods for removing the last image view and removing all image views. This implementation is shown in Listing 3-15.

Listing 3-15.  Action method implementation to remove and clear image views

- (void)removeImageView
{
    if (self.imageViews.count > 0)
    {
        [self.imageViews.lastObject removeFromSuperview];
        [self.imageViews removeLastObject];
        [self rebuildImageViewsConstraints];
    }
}

- (void)clearImageViews
{
    if (self.imageViews.count > 0)
    {
        for (int i=self.imageViews.count - 1; i >= 0; i--)
        {
            UIImageView *imageView = [self.imageViews objectAtIndex:i];
            [imageView removeFromSuperview];
            [self.imageViews removeObjectAtIndex:i];
        }

        [self rebuildImageViewsConstraints];
    }
}

The methods in Listing 3-15 are fairly straightforward. In the removeImageView action, check to see if there are any image views. If there are image views, remove that object from the view and then remove it from the array that contains it. The clearImageViews does the same thing the removeImageView action does, except it steps through every image view in the array and removes them one at a time. Both methods rebuild the constraints once they are finished removing image views.

Your app is now finished. If you build and run it, you should be able to repeatedly add and remove image views using the three buttons. Figure 3-26 shows an example in which we’ve added several image views.

9781430259596_Fig03-26.jpg

Figure 3-26. Overlapping image views positioned using Auto Layout

As Figure 3-27 shows, thanks to Auto Layout, the app works equally well in landscape orientation.

9781430259596_Fig03-27.jpg

Figure 3-27. Auto Layout automatically adjusts the layout when rotated into landscape orientation

Recipe 3-3: Debugging Auto Layout Code

Auto Layout errors in code can be quite difficult to work out because you can’t visually see the errors like you can in Interface Builder. The general advice is to think carefully about the constraints before you start coding them. However, to minimize the time spent in trial-and-error mode, it’s important to know what’s actually wrong with a problematic layout. In other words, you need to know how to debug it.

Besides syntax errors in Visual Format strings, there are two major ways in which an Auto Layout code might fail. The first is unsatisfiability, which means there are two or more constraints conflicting with each other in such a way that the layout engine can’t simultaneously satisfy them. The second way is caused by ambiguity. That happens when the defined constraints aren’t specific enough, leaving the layout engine with several possible values for a property.

Note   When you do autolayout programmatically, errors fall under the category of unsatisfiability (conflicting constraints) or ambiguity when creating layouts visually in the interface builder. There is a third type of error called misplaced views, which indicates mismatched position or size. The orange lines in the visual editor indicate all these errors.

In this recipe, we show you examples of both unsatisfiable and ambiguous constraints. We’ll show you how to identify and tackle them.

Dealing with Ambiguous Layouts

To get started, you need a new project, so create one using the single view application template. We’ll begin with a simple example of an ambiguous layout. Let’s say you want to programmatically add three buttons of equal size to the top of the screen. In the view controller’s viewDidLoad method, add the code in Listing 3-16 to create the buttons and add them to the main view.

Listing 3-16.  Creating buttons and adding them to the main view

- (void)viewDidLoad
{

    [superviewDidLoad];
UIButton *button1 = [UIButtonbuttonWithType:UIButtonTypeSystem];
    [button1 setTitle:@"Button 1"forState:UIControlStateNormal];
    button1.translatesAutoresizingMaskIntoConstraints = NO;
    [self.viewaddSubview:button1];

UIButton *button2 = [UIButtonbuttonWithType:UIButtonTypeSystem];
    [button2 setTitle:@"Button 2"forState:UIControlStateNormal];
    button2.translatesAutoresizingMaskIntoConstraints = NO;
    [self.viewaddSubview:button2];

UIButton *button3 = [UIButtonbuttonWithType:UIButtonTypeSystem];
    [button3 setTitle:@"Button 3"forState:UIControlStateNormal];
    button3.translatesAutoresizingMaskIntoConstraints = NO;
    [self.viewaddSubview:button3];
}

Then add constraints to pin the buttons to the top of the screen, as shown in Listing 3-17.

Listing 3-17.  Adding constraints to pin buttons to the top of the screen

- (void)viewDidLoad
{
    [super viewDidLoad];

    // ...

    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(button1, button2, button3);

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[button1]"
        options:0 metrics:nil views:viewsDictionary]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[button2]"
        options:0 metrics:nil views:viewsDictionary]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[button3]"
        options:0 metrics:nil views:viewsDictionary]];
}

Note    NSDictionaryOfVariableBindings() is a convenience function that creates the dictionary needed by NSLayoutConstraint to map identifiers in your visual format strings to the views. It creates entries for the provided views using the variable names as keys.

Finally, add constraints for the horizontal layout, pinning the buttons to each other and to the bounds of the screen, as shown in Listing 3-18.

Listing 3-18.  Adding constraints for pinning buttons to each other and screen bounds horizontally

- (void)viewDidLoad
{
    [super viewDidLoad];

    // ...

    [self.view addConstraints:[NSLayoutConstraint
        constraintsWithVisualFormat:@"|-[button1]-[button2]-[button3]-|"
        options:0 metrics:nil views:viewsDictionary]];
}

Build and run the app, expecting to see your buttons in a nice row at the top of the screen. The buttons show up, but when you rotate the screen, as shown in Figure 3-28, one of the buttons gets significantly wider than the other two.

9781430259596_Fig03-28.jpg

Figure 3-28. Due to ambiguous constraints, one of the buttons is wider than the other two

What’s going on here? Your first thought when something like this happens might be that you have ambiguous constraints. To verify if that’s the case, leave the app running but go back to Xcode and click the “Pause Program Execution” button (see Figure 3-29) in the Debug Area toolbar.

9781430259596_Fig03-29.jpg

Figure 3-29. The “Pause Program Execution” button in Xcode

With the program paused, you can then use the (lldb) prompt to enter the following command:

po [[UIWindow keyWindow] _Auto LayoutTrace]

You then get a trace showing that the three buttons indeed have ambiguous layouts (see Figure 3-30.)

9781430259596_Fig03-30.jpg

Figure 3-30. An Auto Layout trace indicating ambiguous layouts

Note   po (or print-object) is a debugger command that prints out the description text of an object. It can be a very useful tool when debugging your application.

So what’s the problem? Usually, when it comes to ambiguous layouts the problem is a sign that you’re missing one or more constraints. The problem in this case is that you haven’t specified the widths of the buttons specifically enough. All you’ve said is that the buttons should be pinned to each other and to the edges of the screen, so when the size of the screen increases, the layout engine has several options: it can increase the width of the first button, it can increase the width of the second button, and so on.

What you want, though, is to have buttons of equal widths. To solve the problem, simply add constraints specifying that button2 and button3 are the same width as button1, as shown in Listing 3-19.

Listing 3-19.  Setting button2 and button3 to equal widths

- (void)viewDidLoad
{
    [super viewDidLoad];

    // ...

    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(button1, button2, button3);

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[button1]"
        options:0 metrics:nil views:viewsDictionary]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[button2]"
        options:0 metrics:nil views:viewsDictionary]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[button3]"
        options:0 metrics:nil views:viewsDictionary]];

    [self.view addConstraints:[NSLayoutConstraint
        constraintsWithVisualFormat:@"|-[button1]-[button2(==button1)]-[button3(==button1)]-|"
        options:0 metrics:nil views:viewsDictionary]];
}

This leaves the layout engine with only one option: to increase the widths equally for all three buttons. Now when you build and run, you’ll get the expected result, as in Figure 3-31.

9781430259596_Fig03-31.jpg

Figure 3-31. A user interface with constraints specifying the buttons to be of equal width

Handling Unsatisfiability

The opposite of ambiguous constraints is unsatisfiable constraints. In those cases, the probable cause is not too few, but rather too many constraints, or that two constraints conflict. This can be a little trickier to solve because you’ve added those constraints for a purpose. Therefore, the unsatisfiability might be a sign you’ve made logical errors and need to rethink the entire layout.

However, let’s start with a simple yet common mistake that occurs in unsatisfiable constraints. Let’s say you forgot to set translatesAutoresizingMaskIntoConstraints to NO for one of your buttons. That usually results in conflicting constraints between the ones the framework adds (for the autoresizing mask) and your own.

To see what happens, comment out the row in Listing 3-20 that sets the translatesAutoresizingMaskIntoConstraints property of your third button.

Listing 3-20.  Commenting out the translatesAutoresizingMaskIntonContraints property for button3

- (void)viewDidLoad
{
    [super viewDidLoad];

    // ...

    UIButton *button3 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [button3 setTitle:@"Button 3" forState:UIControlStateNormal];
// button3.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:button3];

    // ...
}

If you build and run now, you’ll see that the buttons seem to have disappeared from the screen, as in Figure 3-32.

9781430259596_Fig03-32.jpg

Figure 3-32. Forgetting to turn off the automatic creation of autoresizing mask constraints can result in unsatisfiable constraints errors

If you look in the error log, you’ll see a long error text starting with the reason for the failure:

2013-07-05 17:15:53.268 Recipe 3-3: Debugging Auto Layout[40301:c07] 
Unable to simultaneously satisfy constraints.

Probably at least one of the constraints in the list in the log is one you don't want. Try this: (1) look at each constraint and try to figure out which one 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:0x8ded860 H:|-(NSSpace(20))-[UIButton:0x8df8070]   (Names: '|':UIView:0x8df7ee0 )>",
    "<NSLayoutConstraint:0x8ded8b0 H:[UIButton:0x8df8070]-(NSSpace(8))-[UIButton:0x8d75230]>",
    "<NSLayoutConstraint:0x8def8a0 UIButton:0x8d75230.width == UIButton:0x8df8070.width>",
    "<NSLayoutConstraint:0x8def8d0 H:[UIButton:0x8d75230]-(NSSpace(8))-[UIButton:0x8ddf420]>",
    "<NSLayoutConstraint:0x8def910 UIButton:0x8ddf420.width == UIButton:0x8df8070.width>",
    "<NSAutoresizingMaskLayoutConstraint:0x8d6f390 h=--& v=--& UIButton:0x8ddf420.midX ==>"
)

Indeed, you seem to have NSAutoresizingMaskLayoutConstraints associated with one of your buttons. So there’s your problem. Uncomment the row you previously commented out and rerun your application. It should now work as previously.

Now, let’s create another example of an unsatisfiable layout. Let’s say you want to change the layout from the preceding section (the one with three buttons) so that the button widths don’t grow beyond 100 points wide when the screen rotates. Add the width constraint shown in Listing 3-21.

Listing 3-21.  Creating a constraint on all the buttons to limit their growth to 100 points

[self.view addConstraints:[NSLayoutConstraint
   constraintsWithVisualFormat:@"|-[button1(<=100)]-[button2(==button1)]-[button3(==button1)]-|"
   options:0 metrics:nil views:viewsDictionary]];

If you build and run this, the buttons look great in portrait mode, but when you rotate the screen a strange thing happens. As Figure 3-33 shows, the first two buttons get aligned to the left, while the third button is pinned to the right side of the screen.

9781430259596_Fig03-33.jpg

Figure 3-33. Casually adding a width constraint can have unexpected results

Again, the error log indicates that you are dealing with unsatisfiable constraints. Let’s take a closer look at the involved constraints:

(
    "<NSLayoutConstraint:0x8da7220 H:|-(NSSpace(20))-[UIButton:0x8da3bd0]   (Names: '|':UIView:0x8da3a40 )>",
    "<NSLayoutConstraint:0x8da7370 H:[UIButton:0x8da3bd0(<=100)]>",
    "<NSLayoutConstraint:0x8da73b0 H:[UIButton:0x8da3bd0]-(NSSpace(8))-[UIButton:0x8d38fd0]>",
    "<NSLayoutConstraint:0x8da73e0 UIButton:0x8d38fd0.width == UIButton:0x8da3bd0.width>",
    "<NSLayoutConstraint:0x8da7410 H:[UIButton:0x8d38fd0]-(NSSpace(8))-[UIButton:0x8d3e3f0]>",
    "<NSLayoutConstraint:0x8da7440 UIButton:0x8d3e3f0.width == UIButton:0x8da3bd0.width>",
    "<NSLayoutConstraint:0x8da7470 H:[UIButton:0x8d3e3f0]-(NSSpace(20))-|   (Names: '|':UIView:0x8da3a40 )>",
    "<NSAutoresizingMaskLayoutConstraint:0x8da7160 h=--& v=--& V:[UIView:0x8da3a40(480)]>"
)

The two NSAutoresizingMaskLayoutConstraint entries are associated with the main view and are fine (you shouldn’t turn the translatesAutoresizingMaskIntoConstraints on for root views). But the others give clues about what’s going wrong. The problem here is that you have pinned the group of buttons to the screen edges. So when the screen rotates, the button widths will grow beyond 100 pixels and the layout engine can’t satisfy the width constraint you added.

What you need to do here is to rethink the layout. What do you want?

  • Buttons of equal widths?
  • Buttons positioned next to each other, with the default spacing?
  • The left and right buttons pinned to the respective screen edges, unless that causes the button widths to grow beyond 100 pixels? In that case, do you want the group of buttons to stay centered in the screen?

The key here is in the third point where “unless” indicates that you should use non-required constraints; however, let’s start with the first two points. They can be expressed in the same visual format string, as shown in bold in Listing 3-22.

Listing 3-22.  The new visual format string

 [self.view addConstraints:[NSLayoutConstraint
    constraintsWithVisualFormat:@"[button1(<=100)]-[button2(==button1)]-[button3(==button1)]"
    options:0 metrics:nil views:viewsDictionary]];

You might be wondering what exactly we did here that changed from Listing 3-21. Look carefully and you will see that the left and right screen edge constraints have been removed (designated by “|-“ and ”-|”).

Note   You might be wondering why we didn’t also pin button1 to the left edge of the screen and button3 to the right in the preceding format string. The reason is that those constraints should be non-required, but in Visual Format Language you can only set priorities (for example, making them non-required) for size constraints. It’s not possible to set priorities for constraints that operate on properties such as leading and trailing edges.

Next, you need to loosely pin the group of buttons to the screen edges (with a 20-point spacing), as shown in Listing 3-23.

Listing 3-23.  Adding loose constraints to the screen edges with 20-point spacing

NSLayoutConstraint *pinToLeft =
    [NSLayoutConstraint
        constraintWithItem:button1 attribute:NSLayoutAttributeLeading
        relatedBy:NSLayoutRelationEqual
        toItem:self.view attribute:NSLayoutAttributeLeading
        multiplier:1 constant:20];
pinToLeft.priority = 500;
[self.view addConstraint:pinToLeft];

NSLayoutConstraint *pinToRight =
    [NSLayoutConstraint
        constraintWithItem:button3 attribute:NSLayoutAttributeTrailing
        relatedBy:NSLayoutRelationEqual
        toItem:self.view attribute:NSLayoutAttributeTrailing
        multiplier:1 constant:20];
pinToRight.priority = 500;
[self.view addConstraint:pinToRight];

Finally, you need the rule that tells the group to center in the screen. This can be a required constraint because it is true even if the group is pinned to the screen edges. Add the code shown in Listing 3-24.

Listing 3-24.  Creating a center constraint for the group

NSLayoutConstraint *center =
    [NSLayoutConstraint
        constraintWithItem:button2 attribute:NSLayoutAttributeCenterX
        relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX
         multiplier:1 constant:0];
[self.view addConstraint:center];

Here’s the resulting viewDidLoad method, with changes marked in bold:

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIButton *button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [button1 setTitle:@"Button 1" forState:UIControlStateNormal];
    button1.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:button1];

    UIButton *button2 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [button2 setTitle:@"Button 2" forState:UIControlStateNormal];
    button2.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:button2];

    UIButton *button3 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [button3 setTitle:@"Button 3" forState:UIControlStateNormal];
    button3.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:button3];

    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(button1, button2, button3);

    [self.view addConstraints:
     [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[button1]" options:0 metrics:nil views:viewsDictionary]];
    [self.view addConstraints:
     [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[button2]" options:0 metrics:nil views:viewsDictionary]];
    [self.view addConstraints:
     [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[button3]" options:0 metrics:nil views:viewsDictionary]];

    [self.view addConstraints:[NSLayoutConstraint
       constraintsWithVisualFormat:@"[button1(<=100)]-[button2(==button1)]-[button3(==button1)]"
       options:0 metrics:nil views:viewsDictionary]];

    NSLayoutConstraint *pinToLeft = [NSLayoutConstraint
        constraintWithItem:button1 attribute:NSLayoutAttributeLeading
        relatedBy:NSLayoutRelationEqual
        toItem:self.view attribute:NSLayoutAttributeLeading
        multiplier:1 constant:20];
    pinToLeft.priority = 500;
    [self.view addConstraint:pinToLeft];

    NSLayoutConstraint *pinToRight = [NSLayoutConstraint
        constraintWithItem:button3 attribute:NSLayoutAttributeTrailing
        relatedBy:NSLayoutRelationEqual
        toItem:self.view attribute:NSLayoutAttributeTrailing
        multiplier:1 constant:20];
    pinToRight.priority = 500;
    [self.view addConstraint:pinToRight];

    NSLayoutConstraint *center = [NSLayoutConstraint
        constraintWithItem:button2 attribute:NSLayoutAttributeCenterX
        relatedBy:NSLayoutRelationEqual
        toItem:self.view attribute:NSLayoutAttributeCenterX
        multiplier:1 constant:0];
    [self.view addConstraint:center];
}

Now you can build and run your application again. When rotated to landscape orientation, it should look as it did in portrait orientation, and there should be no errors in the error log. The user interface should resemble Figure 3-34.

9781430259596_Fig03-34.jpg

Figure 3-34. A layout with maximum button widths correctly set

Summary

In this chapter, you learned the basics of Auto Layout and how to use it to build dynamic user interfaces that adapt to changes in screen size and orientation. You set up constraints in Interface Builder as well as in code. You also looked at the two error states, ambiguous constraints and unsatisfiable constraints, and how to debug them.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset