5. View Constraints

The iOS 6 software development kit (SDK) has revolutionized view layout. The days of sizing masks, struts, and springs are over. If you aren’t familiar with those, don’t worry. If you are familiar with them, you’re about to learn something wonderful, fresh, and new. Apple’s layout features will make your life easier and your interfaces more consistent, regardless of device geometry and orientation. This chapter introduces code-level constraint development. You’ll discover how to create relations between on-screen objects and specify the way iOS automatically arranges your views. The outcome is a set of robust rules that adapt to screen geometry.

What Are Constraints?

Constraints are rules that allow you to describe your layout to iOS. Supported only by iOS 6 and later, they limit how things relate to each other, restricting how they may be laid out. With constraints, you can say “these items are always lined up in a horizontal row” or “this item resizes itself to match the height of that item.” Constraints provide a layout language that you add to views to describe visual relationships.

iOS takes charge of meeting those demands via a constraint satisfaction system. The rules must make sense. A view cannot be both to the left and the right of another view. So, one of the key challenges when working with constraints is ensuring that the rules are rigorously consistent. When your rules fail, they fail loudly. Xcode provides you with verbose updates explaining what might have gone wrong.

Another key challenge is making sure your rules are specific enough. An underconstrained interface can create random results when faced with many possible layout solutions. You might request that one view lies to the right of the other, but unless you tell the system otherwise, you might end up with the right view at the top of the screen and the left view at the bottom. That one rule doesn’t say anything about vertical orientation.

Constraints allow you to design resolution independent apps. I worked on a constraint-based iOS 6 application prior to the iPhone 5 introduction. This app required no code updates to work on the new device. All I had to add was a [email protected] file and my app was ready to ship on all device aspects. Constraints ensured my screens were laid out as well on a 4″ screen as they were on a 3.5″ one. If you’re new to iPhone 5 development, make sure your window stretches to all sides, especially if you use Interface Builder (IB).

The iOS 6.x SDK enables you to lay out constraints both visually in Interface Builder and programmatically in your application source code. The IB approach is simple to use and easy to lay out. This chapter focuses on code. It offers code-centered examples that help you craft common view constraints in Objective-C.


Note

Constraints are an iOS 6 feature. Should you attempt to run them on an earlier version of iOS, you will encounter exceptions. To write backward-compatible applications that leverage constraints, make sure to implement two versions of your layout routines: one for iOS 6 and later, and one with autoresizing for earlier deployments. iOS 6 still supports autoresizing layouts. If you use these exclusively, your code will work on earlier systems, but you will miss out on the power of constraints. As Apple continues to introduce new hardware configurations and aspect ratios, you will quickly realize why constraints are the future of layout.


Alignment Rectangles

Say goodbye to frames, at least for the moment. The world of constraints doesn’t focus on frames, the layout rectangles introduced in Chapter 4, “Assembling Views and Animations.” Frames say where to place views on the screen and how big those views will be. Instead, constraints use a geometric element called an alignment rectangle.

As developers create complex views, they may introduce visual ornamentation such as shadows, exterior highlights, reflections, and engraving lines. As they do, these features usually become attached as subviews or sublayers. As a consequence, a view’s frame, its full extent, grows as items are added.

The alignment rectangle, in contrast, is limited to a core visual. Its size remains unaffected as new items join the primary view. Consider Figure 5-1 (left). It depicts a switch with an attached shadow, which is placed behind and offset from the main switch elements. When laying out this shadowed switch, you want to focus on aligning just the switch.

Image

Figure 5-1. A view’s alignment rectangle (center) refers strictly to the core visual element to be aligned, without embellishments.

The central image indicates the switch’s alignment rectangle. This rectangle excludes all ornamentation, including the drop shadow that appears offset to its bottom and right. Instead, it includes the core views that make up the functional part of the switch. Contrast this with the view’s frame, shown in the right image of Figure 5-1.

This frame encompasses all the view’s visual elements, including the shadow. The frame, therefore, is much larger than the switch itself. This shadow throws off the view’s alignment features (for example, its center, bottom, and right). The frame’s x- and y-midpoints are slightly too far to the right and bottom to properly use them for a visually pleasing alignment. The same goes for the right and bottom edges, which don’t touch the core switch.

By working with alignment rectangles instead of frames, iOS’s constraints system ensures that key features like a view’s edges and center are properly considered during layout.

Declaring Alignment Rectangles

When building ornamented views, you have the responsibility to report geometry details to auto layout. Implement alignmentRectForFrame:. This method allows your views to declare accurate alignment rectangles when they use ornamentation such as shadows or reflections.

This method takes one argument, a frame. This argument refers to the destination frame that the view will inhabit; think the rectangle on the right in Figure 5-1. That frame will encompass the entire view, including any ornamentation attached to the view. It’s up to you to provide an accurate representation of the alignment rectangle with respect to that destination frame and your view’s embedded elements.

Your method returns a CGRect value that specifies the rectangle for your view’s core visual geometry, as the center rectangle in Figure 5-1 does. This is typically the main visual object’s frame and excludes any ornamentation views you have added as subviews or into your view’s layer as sublayers.

When planning for arbitrary transformations, make sure to implement frameForAlignmentRect: as well. This method describes the inverse relationship, producing the resulting fully ornamented frame (for example, Figure 5-1, right image) when passed constrained alignment rectangle (for example, Figure 5-1, center image). You extend the bounds to include any ornamentation items in your view, scaling them to the alignment rectangle passed to this method.

Constraint Attributes

Constraints work with a limited geometric vocabulary. Attributes are the “nouns” of the constraint system, describing positions within a view’s alignment rectangle. Relations are “verbs,” specifying how the attributes compare to each other.

The attribute nouns speak to physical geometry. iOS constraints offer the following view attribute vocabulary:

left, right, top, and bottom—The edges of a view’s alignment rectangle on the left, right, top, and bottom of the view.

leading and trailing—The leading and trailing edges of the view’s alignment rectangle. In left-to-right (English-like) systems, these correspond to “left” (leading) and “right” (trailing). In right-to-left linguistic environments like Arabic or Hebrew, these roles flip; right is trailing, and left is leading.

width and height—The width and height of the view’s alignment rectangle.

centerX and centerY—The x-axis and y-axis centers of the views’ alignment rectangle

baseline—The alignment rectangle’s baseline, typically a set offset above its bottom attribute.

The relation verbs compare values. Constraint math is limited to three relations: setting equality or setting lower and upper bounds for comparison. The layout relations you can use are:

Less-than inequalityNSLayoutRelationLessThanOrEqual

EqualityNSLayoutRelationEqual

Greater-than inequalityNSLayoutRelationGreaterThanOrEqual

This might not sound like a lot expressively. When you consider the math you’re building, however, these three cover all the ground needed for equality and inequality relationships.

Constraint Math

All constraints, regardless of how they are created, are essentially equations of the form:

y (relation) m * x + b

If you have a math background, you may have seen a form more like this, with R referring to the relation between y and the computed value on the right side:

y R m * x + b

Y and x are view attributes of the kind you just read about, such as width or centerY or top. M is a constant scaling factor, and b is a constant offset. For example, you might say, “View 2’s left side should be placed 15 points to the right of View 1’s right side.” The relation equation that results is something like this:

View 2’s left = View 1’s right + 15

Here, the relation is equality, the constant offset (b) is 15 and the scaling factor or multiplier (m) is 1. I’ve taken care here, to keep the equation above from looking like code because, as you’ll see, this is not how you declare your constraints in Objective-C.

Constraints do not have to use strict equalities. You can use inequality relations as well. For example, you might say, “View 2’s left side should be place at least 15 points to the right of View 1’s right side,” or

View 2’s left >= View 1’s right + 15

Offsets let you place fixed gaps between items, and the multipliers let you scale. Scaling proves especially useful when laying out grid patterns, letting you multiply off the height of a view, not just adding a fixed distance to the next view.

The Laws of Constraints

Although you can think of constraints as hard “math,” they’re actually just preferences. iOS finds layouts solution that best match your constraints; this solution may not always be unique. Here are a few basic facts about constraints:

Constraints are relationships, not directional. You don’t have to solve the right side to calculate the left side.

Constraints have priorities. Priorities range numerically from 0 to 1000. Higher priorities are always satisfied before those with lower priorities. The highest priority you can assign is “required,” (that is, UILayoutPriorityRequired) (value: 1000), which is also the default.

A required priority should be satisfied exactly; for example, this button is exactly this size. So, when you assign a different priority, you’re actually attenuating that constraint’s sway within the overall layout system.

Even required priorities may be overridden when constraints come into conflict. Don’t be shocked if your 100x100 view ends up being presented at 102x107 if your constraints aren’t perfectly balanced. Table 5-1 details several priority presets and their values.

Table 5-1. Priority Constraints

Image

The autolayout system uses priorities to sort constraints. A priority of 99 is always considered after a priority of 100. During layout, the system iterates through any constraints you have added, attempting to satisfy them all. Priorities come into play when deciding which constraint has less sway. The 99 priority constraint will be broken in favor of the 100 priority constraint should the two come into conflict.

Constraints don’t have any natural “order” outside of priorities. All constraints of the same priority are considered at the same time. If you need some constraint to take precedence, assign it a higher priority.

Constraints can be approximated. Optional constraints try to optimize their results. Consider the constraint “View 2’s top edge should be at the same position as View 1’s bottom edge.” The constraint system attempts to squeeze these two together by minimizing their distance. Should other constraints prevent them from touching, the system places them as close it can, minimizing the absolute distance between the two attributes.

Constraints can have cycles. So long as all items are satisfied, it doesn’t matter which elements refer to which. Don’t sweat the cross-references. In this declarative system, circular references are okay, and you will not encounter infinite looping issues.

Constraints are not completely animatable. In the iOS 6 6gm release, UIView animation or Core Animation can adjust elements as constraints update. Call layoutIfNeeded on the superview or window. This support is not yet universal. Some constrained elements may jump instead of animate to their new positions.

Constraints can cross hierarchies. You can normally align the center point of one view’s subview with the center point of an entirely different view as long as both views belong to the same parent. For example, you might create a complex text entry view and align its rightmost button’s right attribute with the right attribute of an embedded image view below it. There’s just one limitation here, which follows next.

Constraints cannot cross bounds systems. You cannot cross into and out of scroll views and table views for alignment. If there’s some sort of content view with its own bounds system, you can’t hop out of that to an entirely different bounds system in another view.

Constraints can fail at runtime. If your constraints cannot be resolved (there are examples for you at the end of this chapter), and they come into conflict, the runtime system chooses which constraints to disregard to present whatever view layout it can. This is usually ugly and nearly always not the visual presentation you intended. iOS sends exhaustive descriptions of what went wrong to your Xcode console. Use these reports to fix your constraints and bring them into harmony with each other.

Badly formed constraints may interrupt application execution. Unlike constraint conflicts, which produce error messages but allow your application to continue running, some constraint calls may actually crash your application. For example, if you pass a constraint format string such as @"V[view1]-|" (it is missing a colon after the letter V) to a constraint creation method, you’ll encounter a runtime exception. This error cannot be detected during compilation; you must carefully check your format strings by hand. Designing constraints in IB helps avoid bad-typo scenarios:

Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: 'Unable to parse constraint format'


Note

Working with constraints adds all sorts of wonderful and new ways to crash your apps at runtime. Take extreme care when developing and testing your constraint-based application and build tests to ensure that you check their operations in as many scenarios as possible. Let’s hope that someone builds a constraint validator to catch, at least, simple typos like @"H:[myView", a visual format constraint that is missing a closing square bracket.


Creating Constraints

The NSLayoutConstraint class lets you create constraints in two ways. You can use a rather long method call to constrain one item’s attribute to another, explaining how these attributes relate, or you can apply a rather nifty visual formatting language to specify how items are laid out along vertical and horizontal lines.

This section demonstrates both approaches, allowing you to see what they look like and how they are used. Remember this: Regardless of how you build your constraints, they all produce “y relation mx + b” results. All constraints are members of the NSLayoutConstraint class, no matter how you create them.

Basic Constraint Declarations

The NSLayoutConstraint’s class method constraintWithItem:attribute:relatedBy: toItem:attribute:multiplier:constant: (gesundheit) creates a single constraint at a time. These constraints relate one item to another.

The creation method produces a strict view.attribute R view.attribute * multiplier + constant relation, where R is one of equal-to (==), greater-than-or-equal-to (>=), or less-than-or-equal-to <= relations.

Consider the following example:

[self.view addConstraint:
    [NSLayoutConstraint
        constraintWithItem: self.view
        attribute: NSLayoutAttributeCenterX
        relatedBy: NSLayoutRelationEqual
        toItem: textfield
        attribute: NSLayoutAttributeCenterX
        multiplier: 1.0f
        constant: 0.0f]];

This call adds a new constraint to a view controller’s view (self.view). It horizontally center-aligns a text field within this view. It does this by setting an equality relation (NSLayoutRelationEqual) between the two view’s horizontal centers (NSLayoutAttributeCenterX attributes). The multiplier here is 1, and the offset constant is 0. This relates to the following equation:

[self.view]’s centerX = ([textfield]’s centerX * 1) + 0.

It basically says, please ensure that my view’s center and the text field’s center are co-aligned at their X positions.

The UIView’s addConstraint: method adds that constraint to the view, where it is stored with any other constraints in the view’s constraints property.

Visual Format Constraints

The preceding section showed you how to create single constraint relations. A second NSLayoutConstraint class method builds constraints using a text-based visual format language. Think of it as ASCII art for Objective-C nerds. Here’s a simple example:

[self.view addConstraints: [NSLayoutConstraint
    constraintsWithVisualFormat:@"V:[leftLabel]-15-[rightLabel]"
    options:0
    metrics:nil
    views:NSDictionaryOfVariableBindings(leftLabel, rightLabel)]];

This request creates all the constraints that satisfy the relation or relations specified in the visual format string. These strings, which you will see in many examples in following sections, describe how views relate to each other along the horizontal (H) or vertical (V) axis. This example basically says, “Ensure that the right label appears 15 points below the left label.”

You want to note several things about how this constraints formatting example is created:

• The axis is specified first as a prefix, either H: or V:.

• The variable names for views appear in square brackets in the strings.

• The fixed spacing appears between the two as a number constant, -15-.

• This example does not use any format options (the options parameter), but here is where you would specify whether the alignment is done left to right, right to left, or according to the leading-to-trailing direction for a given locale discussed earlier in this chapter.

• The metrics dictionary, also not used in this example, lets you supply constant numbers into your constraints without having to create custom formats. For example, if you want to vary the spacing between these two text labels, you could replace 15 with a metric name (for example, labelOffset, or something like that) and pass that metric’s value in a dictionary. Set the name as the key, the value as a NSNumber. Passing dictionaries (for example, @{@"labelOffset", @15}) proves to be a lot easier than creating new format NSString instances for each width you might use.

• The views: parameter does not, despite its name, pass an array of views. It passes a dictionary of variable bindings. This dictionary associates variable name strings with the views they represent. This indirection allows you to use developer-meaningful symbols like "leftLabel" and "rightLabel" in your format strings.

Building constraints with formats strings always creates an array of results. Some format strings are quite complex, others simple. It’s not always easy guessing how many constraints will be generated from each string. Be aware that you will need to add the entire collection of constraints to satisfy the format string that you processed.

Variable Bindings

When working with visual constraints, the layout system needs to be able to associate view names like "leftLabel" and "rightLabel" with the actual views they represent. Enter variable bindings, via a handy macro defined in NSLayoutConstraint.h, which is part of the UIKit headers.

The NSDictionaryOfVariableBindings() macro accepts an arbitrary number of local variable arguments. As you can see in the earlier example, these need not be terminated with a nil. The macro builds a dictionary from the passed variables, using the variable names as keys and the actual variables as values. For example, the function call

NSDictionaryOfVariableBindings(leftLabel, rightLabel)

builds this dictionary:

@{@"leftLabel":leftLabel, @"rightLabel":rightLabel}.

If you’d rather not use the variable bindings macro, you can easily create a dictionary by hand and pass it to the visual format constraints builder.

Format Strings

The format strings you pass to create constraints follow a basic grammar, which is specified as follows. The question marks refer to optional items, the asterisk to an item that may appear zero or more times:

(<orientation>:)? (<superview><connection>)? <view>(<connection><view>)*
(<connection><superview>)?

Although daunting to look at, these strings are actually quite easy to construct. The next sections offer an introduction to format string elements and provide copious examples of their use.

Orientation

You start with an optional orientation, either H: for horizontal or V: for vertical alignment. This specifies whether the constraint applies left and right or top and down. Consider this constraint: "H:[view1][view2]". It says to place View 2 directly to the right of View 1. The H specifies the alignment the constraint follows. Figure 5-2 (left) shows an interface that uses this rule.

Image

Figure 5-2. Possible layout results for "H:[view1][view2]" (left) and "V:[view1]-20-[view2]-20-[view3]" (right). The left sides of views 1 through 3 were aligned in the image on the right to better show the gaps between each view.

The following is an example of a vertical layout: "V:[view1]-20-[view2]-20-[view3]". It leaves a gap of 20 points below View 1 before placing View 2, followed again by a 20 point gap before View 3. Figure 5-2 (right) shows what this might look like.

The two screenshots in Figure 5-2 depict just two possible layouts for these rules. These views are severely under constrained. Although View 2 appears to the direct right of View 1 in the left image, its vertical placement is basically random. The same kind of underspecification is shown in the image to the right, where the vertical distances are constrained, but not their starting point.

Here are the two constraints that were used in separate executions to create the images shown in Figure 5-2:

[self.view addConstraints:[NSLayoutConstraint
    constraintsWithVisualFormat:@"H:[view1][view2]"
    options:0 metrics:nil
    views:NSDictionaryOfVariableBindings(view1, view2)]];

and

[self.view addConstraints:[NSLayoutConstraint
    constraintsWithVisualFormat:@"V:[view1]-20-[view2]-20-[view3]"
    options:0 metrics:nil
    views:NSDictionaryOfVariableBindings(view1, view2, view3)]];

Note that I manually aligned the left sides of the second example so that you could better see the gaps between each of the three views. Without that intervention, the views could have appeared anywhere along the horizontal span of their parent view.


Note

During debugging, you can use the constraintsAffectingLayoutForAxis: view method to retrieve all the constraints that affect either the horizontal or vertical layout access. Do not ship code with this method. It is not intended for deployment use, and Apple makes it clear that it is not App Store safe.


View Names

As the two examples you just saw demonstrate, views names are encased in square brackets. For example, you might work with "[view1]" and "[view2]". The view name refers to the local variable name of your view. So if you’ve declared

UIButton *button1;

your format string can refer to "[button1]". To make this work, you pass a dictionary of variable bindings to the constraint creation method, which allows you to use the names in this fashion.

You can create this dictionary in two ways. The easiest approach involves using the NSDictionaryOfVariableBindings() macro to create the mapping from variable instances. You can also build the dictionary yourself. This is especially helpful when building formats from arrays of views, as is done in the following code snippet. Once populated, this viewDictionary can be passed as the variable bindings parameter for the formatString:

NSMutableDictionary *viewDictionary = [NSMutableDictionary dictionary];
int i = 1;

for (UIView *view in viewArray)
{
    NSString *viewName = [NSString stringWithFormat:@"view%0d", i++];
    [formatString appendFormat:@"[%@]", viewName];
    [viewDictionary setObject:view forKey:viewName];
}

A special character, the vertical pipe (|) always refers to the superview. You see it only at the beginning or ending of format strings. At the beginning, it appears just after the horizontal or vertical specifier ("V:|..." or "H:|..."). At the end, it appears just before the terminal quote ("...|").

Connections

Place connections between view names to specify the way a layout flows. An empty connection (in other words, one that has been omitted) means “follow on directly.”

The first constraint you saw for Figure 5-2, "H:[view1][view2]" used an empty connection. There’s nothing specified between the square brackets of View 1 and the brackets of View 2. This tells the constraint to place View 2 directly to the right of View 1. Figure 5-3 shows what that relationship might look like in use.

Image

Figure 5-3. "H:[view1][view2]" uses an empty connection, placing both views directly next to each other.

A hyphen (-) represents a small fixed space. The constraint "H:[view1]-[view2]" uses a hyphen connection. This constraint leaves a small gap between View 1 and View 2, as shown in Figure 5-4.

Image

Figure 5-4. "H:[view1]-[view2]" adds a spacer connection.

Place a numeric constant between hyphens to set an exact gap size. The constraint "H:[view1]-30-[view2]" adds a 30-point gap between the two views, as shown in Figure 5-5. This is visibly wider than the small default gap produced by the single hyphen.

Image

Figure 5-5. "H:[view1]-30-[view2]" uses a fixed-size gap of 30 points, producing a noticeably wider space.

In constraints specifications, the pipe character always refers to the superview. The pipe can appear at the start/end of the format string. The following example uses both.

The format "H:|[view1]-[view2]|" specifies a horizontal layout that starts with the superview. The superview is immediately followed by the first view, then a spacer, the second view, and then the superview, which you can see in Figure 5-6.

Image

Figure 5-6. "H:|[view1]-[view2]|" tells both views to hug the edges of their superview. With a fixed-size gap between them, at least one of the views must resize to satisfy the constraint.

This constraint left-aligns View 1 and right-aligns View 2 with the superview. To accomplish this, something has to give. Either the left view or the right view must resize to meet these constraints. When I ran the test app, it happened to be View 2 that adjusted, which is what you see in Figure 5-6. It could just as easily have been View 1.

Often, you don’t want to bang up right against the superview edges. A similar constraint, "H:|-[view1]-[view2]-|", leaves an edge inset between the edges of the superview and the start of View 1 and end of View 2 (see Figure 5-7).

Image

Figure 5-7. "H:|-[view1]-[view2]-|" introduces edge insets between the views and their superviews.

These follow standard Interface Builder/Cocoa Touch layout rules and have not yet been exposed in specifics via the iOS API. The inset gaps at the edges are normally slightly larger than the default view-to-view gaps. You can see that in the following layout created from this constraint.

If your goal is to add a flexible space between views, there’s a way to do that too. Add a relation rule between the two views (for example, "H:|-[view1]-(>=0)-[view2]-|") to allow the two views to retain their sizes and separate while maintaining gaps at their edges with the superview, as shown in Figure 5-8. This rule, which equates to “at least 0 points distance,” provides a more flexible way to let the views spread out. I recommend using a small number here so that you don’t inadvertently interfere with a view’s other geometry rules.

Image

Figure 5-8. "H:|-[view1]-(>=0)-[view2]-|" uses a flexible space between the two views, allowing them to separate, while maintaining their sizes.

These constraints are not, of course, limited to just one or two views. You can easily stick in a third, fourth, or more. Consider this constraint: "H:|-[view1]-[view2]-(>=5)-[view3]-|". It adds a third view, separated from the other two views by a flexible space. Figure 5-9 shows what that might look like.

Image

Figure 5-9. "H:|-[view1]-[view2]-(>=5)-[view3]-|" demonstrates a rule that includes three views.

Predicates

The last two examples in the previous section used relationship rules with comparisons. These are also called predicates, an affirmation of the way a relation works between view elements. Predicates appear in parentheses. For example, you might specify the size of a view is at least 50 points using the following format:

[view1(>=50)]

This predicate relates to a single view. Notice that it is included within the view’s square brackets, rather than as part of the connections between views. You’re not limited to a single request. For example, you might use a similar approach to let a view’s size range between 50 and 70 points. When adding compound predicates, separate your rule with commas:

[view1(>=50, <=70)]

Relative relation predicates allow your views to grow. If you want your view to expand across a superview, tell it to size itself to some value greater than zero. The following rule stretches a view horizontally across its superview, allowing only for edge insets at each side:

H:|-[view1(>=0)]-|

Figure 5-10 shows what that constraint looks like when rendered.

Image

Figure 5-10. "H:|-[view1(>=0)]-|" adds a flexibility predicate to the view, letting it stretch across its parent. Edge insets offset it slightly from the superview’s sides.

When you are not actually comparing two things, you can skip the double-equals in your format predicates. For example [view1(==120)] is equivalent to [view1(120)], and [view1]-(==50)-[view2] is the same as [view1]-50-[view2].

Metrics

When you don’t know a constant’s value (like 120 or 50) a priori, use a metric dictionary to provide that value. This dictionary is passed as one of the parameters to the constraint creation method. Here is an example of a format string using a metric stand-in: [view1(>=minwidth)]

The minwidth stand-in must map to an NSNumber value in the passed metric dictionary. For more examples of metric use, refer to Recipe 5-3’s constrainSize: method. It demonstrates how to use metrics, using values from an associated dictionary in its constraints.

View-to-View Predicates

Predicates aren’t limited to numeric constants. You can relate a view’s size to another view, ensuring that it’s no bigger than that view in the layout. This example limits View 2’s extent to no bigger than that of View 1, along whichever axis the constraint is currently using: [view2(<=view1)]

You can’t do a lot more with format strings and view-to-view comparisons. If you want to establish more complex relationships, like those between centers, tops, and heights, skip the visual format strings and use the item constraint constructor instead.

Priorities

Each constraint may specify an optional priority by adding an at sign (@) and a number or metric. For example, you can say that you want a view to be 500 points wide, but that the request has a relatively low priority:

[view1(500@10)]

You place priorities after predicates. Here’s an example of a layout format string with an embedded priority:

[view1]-(>=50@30)-[view2]

Format String Summary

Table 5-2 summarizes format string components used to create constraints for automating view layout.

Table 5-2. Visual Format Strings

Image
Image

Storing and Updating Constraints

All constraints belong to the NSLayoutConstraint class, regardless of how they are created. When working with constraints, you can add them to your views either one by one, using addConstraint: as shown in the previous section, or in arrays by using the addConstraints: instance method (notice the s at the end of the name). In day-to-day work, you often deal with collections of constraints, stored in arrays.

Constraints can be added and removed at any time. The two methods, removeConstraint: and removeConstraints:, enable you to remove one or an array of constraints from a given view. Because these methods work on objects, they might not do what you expect when you attempt to remove constraints.

Suppose, for instance, that you build a center-matching constraint and add it to your view. You cannot then build a second version of the constraint with the same rules and expect to remove it using a standard removeConstraint: call. They are equivalent constraints, but they are not the same constraint. Here’s an example of this conundrum:

[self.view addConstraint:
    [NSLayoutConstraint constraintWithItem:textField
        attribute:NSLayoutAttributeCenterX
        relatedBy:NSLayoutRelationEqual
        toItem:self.view
        attribute:NSLayoutAttributeCenterX
        multiplier:1.0f constant:0.0f]];
[self.view removeConstraint:
    [NSLayoutConstraint constraintWithItem:textField
        attribute:NSLayoutAttributeCenterX
        relatedBy:NSLayoutRelationEqual
        toItem:self.view
        attribute:NSLayoutAttributeCenterX
        multiplier:1.0f constant:0.0f]];

Executing these two method calls ends up as follows. The self.view instance contains the original constraint, and the attempt to remove the second constraint is ignored. Removing a constraint not held by the view has no effect.

You have two choices for resolving this. First, you can hold onto the constraint when it’s first added by storing it in a local variable. Here’s what that would look like, more or less:

NSLayoutConstraint *myConstraint =
    NSLayoutConstraint constraintWithItem:textField
        attribute:NSLayoutAttributeCenterX
        relatedBy:NSLayoutRelationEqual
        toItem:self.view
        attribute:NSLayoutAttributeCenterX
        multiplier:1.0f constant:0.0f]];
[self.view addConstraint:myConstraint];

// later
[self.view removeConstraint:myConstraint];

Or, you can use a method (see Recipe 5-1) that compares constraints and removes one that numerically matches the one you pass.

Knowing whether your constraints will be static, used for the lifetime of your view, or dynamic, updated as needed, helps you decide which approach you need. If you think you might need to remove a constraint in the future, either hold on to it via a local variable so that you can later remove it from your view or use workarounds like the one detailed in Recipe 5-1.

Here are some basic points you need to know about managing constraints:

• You can add constraints to and remove constraints from view instances. The core methods are addConstraint: (addConstraints:), removeConstraint: (removeConstraints:), and constraints. The last of these returns an array of constraints stored by the view.

• Constraints are not limited to container views. Nearly any view can store constraints. (A class method, requiresConstraintBasedLayout, specifies whether classes depend on constraints to operate properly.)

• If you want to code a subview with constraints, switch off the subview’s translatesAutoresizingMaskIntoConstraints property. You’ll see this in action in the sample code for this chapter and it’s discussed further in the “Debugging Your Constraints” section toward the end of this chapter.

Recipe: Comparing Constraints

All constraints use a fixed structure of the following form, along with an associated priority:

view1.attribute (relation) view2.attribute * multiplier + constant

Each element of this equation is exposed through a constraint’s object properties, namely priority, firstItem, firstAttribute, relation, secondItem, secondAttribute, multiplier, and constant. These properties make it easy to compare two constraints.

Views store and remove constraints as objects. If two constraints are stored in separate memory locations, they’re considered unequal, even if they describe the same conditions. To allow your code to add and remove constraints on-the-fly without storing those items locally, use comparisons.

Recipe 5-1 introduces three methods. The constraint:matches: method compares the properties in two constraints, determining whether they match. Note that only the equation is considered, the priority is not (although you can easily add this if you want), because I thought that two constraints describing the same conditions were essentially equivalent regardless of whatever priority a developer had assigned to them.

The two other methods (constraintMatchingConstraint: and removeMatchingConstraint:) respectively help locate the first matching constraint stored within a view and remove that matching constraint.


Note

Recipe 5-1 implements a UIView class category. This category is used throughout this chapter to provide a set of utility methods you can expand for use in your own applications.


Recipe 5-1. Comparing Constraints


@implementation UIView (BasicConstraints)
 // This ignores any priority, looking only at y (R) mx + b
- (BOOL) constraint: (NSLayoutConstraint *) constraint1
    matches: (NSLayoutConstraint *) constraint2
{
    if (constraint1.firstItem != constraint2.firstItem) return NO;
    if (constraint1.secondItem != constraint2.secondItem) return NO;
    if (constraint1.firstAttribute != constraint2.firstAttribute) return NO;
    if (constraint1.secondAttribute != constraint2.secondAttribute) return NO;
    if (constraint1.relation != constraint2.relation) return NO;
    if (constraint1.multiplier != constraint2.multiplier) return NO;
    if (constraint1.constant != constraint2.constant) return NO;

    return YES;
}

// Find first matching constraint. (Priority, Archiving ignored)
- (NSLayoutConstraint *) constraintMatchingConstraint:
    (NSLayoutConstraint *) aConstraint
{
    for (NSLayoutConstraint *constraint in self.constraints)
        if ([self constraint:constraint matches:aConstraint])
            return constraint;

    for (NSLayoutConstraint *constraint in self.superview.constraints)
        if ([self constraint:constraint matches:aConstraint])
            return constraint;
    return nil;
}

// Remove constraint
- (void) removeMatchingConstraint: (NSLayoutConstraint *) aConstraint
{
    NSLayoutConstraint *match =
        [self constraintMatchingConstraint:aConstraint];
    if (match)
    {
        [self removeConstraint:match];
        [self.superview removeConstraint:match];
    }
}
@end



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 5.


Recipe: Describing Constraints

When developing and debugging constraints, you may find it useful to produce human-readable descriptions of arbitrary NSLayoutConstraints. Recipe 5-2 builds parsimonious strings that describe constraints as equations, as follows:

[UILabel:6bb32a0].right <= [self].right;

[self].width == ([self].height * 1.778);

[UILabel:6bb32a0].leading == ([UILabel:6ed2e70].trailing + 60.000)

This recipe transforms constraint instances into these textual presentations. It does so within the context of the view whose constraints are being considered (and hence the references to self and superview, in addition to specific subviews that are listed by class and memory address).

Notice that not all constraints include two items. Constraints that only refer to themselves (such as the second example, which sets its width as a multiplier of its height) may occur. In these cases, the item2 property is invariably nil.

Recipe 5-2. Describing Constraints


@implementation UIView (BasicConstraints)

// Return a string that describes an attribute
- (NSString *) nameForLayoutAttribute: (NSLayoutAttribute) anAttribute
{
    switch (anAttribute)
    {
        case NSLayoutAttributeLeft: return @"left";
        case NSLayoutAttributeRight: return @"right";
        case NSLayoutAttributeTop: return @"top";
        case NSLayoutAttributeBottom: return @"bottom";
        case NSLayoutAttributeLeading: return @"leading";
        case NSLayoutAttributeTrailing: return @"trailing";
        case NSLayoutAttributeWidth: return @"width";
        case NSLayoutAttributeHeight: return @"height";
        case NSLayoutAttributeCenterX: return @"centerX";
        case NSLayoutAttributeCenterY: return @"centerY";
        case NSLayoutAttributeBaseline: return @"baseline";
        default: return @"not-an-attribute";
    }
}

// Return a name that describes a layout relation
- (NSString *) nameForLayoutRelation: (NSLayoutRelation) aRelation
{
    switch (aRelation)
    {
        case NSLayoutRelationLessThanOrEqual: return @"<=";
        case NSLayoutRelationEqual: return @"==";
        case NSLayoutRelationGreaterThanOrEqual: return @">=";
        default: return @"not-a-relation";
    }
}

// Describe a view in its own context
- (NSString *) nameForItem: (id) anItem
{
    if (!anItem) return @"nil";
    if (anItem == self) return @"[self]";
    if (anItem == self.superview) return @"[superview]";
    return [NSString stringWithFormat:@"[%@:%d]", [anItem class], (int) anItem];
}

// Transform the constraint into a string representation
- (NSString *) constraintRepresentation: (NSLayoutConstraint *) aConstraint
{
    NSString *item1 = [self nameForItem:aConstraint.firstItem];
    NSString *item2 = [self nameForItem:aConstraint.secondItem];
    NSString *relation = [self nameForLayoutRelation:aConstraint.relation];
    NSString *attr1 = [self nameForLayoutAttribute:aConstraint.firstAttribute];
    NSString *attr2 = [self nameForLayoutAttribute:aConstraint.secondAttribute];

    NSString *result;

    if (!aConstraint.secondItem)
    {
        result = [NSString stringWithFormat:@"(%4.0f) %@.%@ %@ %0.3f",
            aConstraint.priority, item1, attr1,
            relation, aConstraint.constant];
    }
    else if (aConstraint.multiplier == 1.0f)
    {
        if (aConstraint.constant == 0.0f)
            result = [NSString stringWithFormat:@"(%4.0f) %@.%@ %@ %@.%@",
                aConstraint.priority, item1, attr1, relation, item2, attr2];
        else
            result = [NSString stringWithFormat:
                @"(%4.0f) %@.%@ %@ (%@.%@ + %0.3f)",
                aConstraint.priority, item1, attr1, relation,
                item2, attr2, aConstraint.constant];
    }
    else
    {
        if (aConstraint.constant == 0.0f)
            result = [NSString stringWithFormat:
                @"(%4.0f) %@.%@ %@ (%@.%@ * %0.3f)",
                aConstraint.priority, item1, attr1, relation,
                item2, attr2, aConstraint.multiplier];
        else
            result = [NSString stringWithFormat:
                @"(%4.0f) %@.%@ %@ ((%@.%@ * %0.3f) + %0.3f)",
                aConstraint.priority, item1, attr1, relation,
                item2, attr2, aConstraint.multiplier, aConstraint.constant];
    }

    return result;
}
@end



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 5.


Recipe: Creating Fixed-Size Constrained Views

When working with constraints, start thinking about your views in a new way. You don’t just set a frame and expect the view to stay where and how big you left it. Constraint layout uses an entirely new set of assumptions.

Here’s how you might have written a utility method to create a label before iOS 6:

- (UILabel *) createLabelTheOldWay: (NSString *) aTitle
{
    UILabel *aLabel = [[UILabel alloc]
       initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 100.0f)];
    aLabel.textAlignment = NSTextAlignmentCenter;
    aLabel.text = aTitle;

    return aLabel;
}

With Cocoa Touch’s new autolayout system, you approach code-level view creation in a new way. Your code adds constraints that adjust the item’s size and position instead of building a fixed frame and setting its center.

Disabling Autosizing Constraints

When you move into the constraints world, you start by disabling a view property that automatically translates autoresizing masks into constraints. As a rule, you either enable this, allowing the view to participate in the constraint system via its autoresizing mask, or you disable it entirely and manually add your own constraints.

Autoresizing refers to the struts and springs layout tools used in IB and to the autoresizing flags like UIViewAutoresizingFlexibleWidth used in code. When you lay out a view’s resizing behavior with these approaches, that view should not be referred to in any constraints you define.

The constraints-specific property in question is translatesAutoresizingMaskIntoConstraints. Setting this to NO ensures that you can add constraints without conflicting with the automated system. This is pretty important. If you fail to disable the property and start using constraints, you’ll generate constraint conflicts. The autoresizing constraints won’t coexist peacefully with ones you write directly. Here’s an example of a runtime error message that results:

2012-06-24 15:34:54.839 HelloWorld[64834:c07] 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:0x6ec9430 H:[UILabel:0x6ec5210(100)]>",
    "<NSAutoresizingMaskLayoutConstraint:0x6b8e2a0
        h=--& v=--& H:[UILabel:0x6ec5210(0)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x6ec9430 H:[UILabel:0x6ec5210(100)]>
Break on objc_exception_throw to catch this in the debugger.

Choosing between autoresizing layout and constraints layout forms an important part of your coding work.

Starting within View Bounds

The first method in Recipe 5-3, constrainWithinSuperviewBounds, requests that a view is placed entirely within its superview’s bounds. It creates four constraints that ensure this. One requires that the view’s left side is at or to the right of the superview’s left side, another that the view’s top is at or below the superview’s top, and so forth.

The reason for creating this method is that in a loosely constrained system it’s entirely possible that your views will disappear offscreen with negative origins. This method basically says, “Please respect the (0,0) origin and the size of the superview when placing my subviews.”

In most real-world development, this set of constraints is not normally necessary. They are particularly useful, however, when you’re first getting started and want to explore constraints from code. They allow you to test out small constraint systems while ensuring the views you’re exploring remain visible so that you can see how they relate to each other.

In addition, as you get up to speed with constraints, you’ll probably want to add some sort of debugging feedback letting you know where your views ended up once your primary view loads and your constraints fire. Consider adding the following loop to your viewDidAppear: method:

- (void) viewDidAppear:(BOOL)animated
{
    for (UIView *subview in self.view.subviews)
        NSLog(@"View (%d) location: %@",
            [self.view.subviews indexOfObject:subview],
                NSStringFromCGRect(subview.frame));
}

Constraining Size

Recipe 5-3’s second method constrainSize: fixes a view’s extent to the CGSize you specify. This is a common task when working with constraints. You cannot just set the frame the way you’re used to. And, again, remember that your constraints are requests, not specific layouts. If your constraints are not well formed, your 100-point-wide text field may end up 107-points wide in deployment, or worse.

You can define constraints that request a specific width or height for a given view, but the sizes for the two constraints in this method can’t be known ahead of time. The method is meant for use across a wide variety of views. Therefore, the sizes are passed to the constraint as metrics. Metrics basically act as numeric constraint variables.

These particular constraints use two metric names: "theHeight" and "theWidth". The names are completely arbitrary. As a developer, you specify the strings, which correspond to keys in the metrics: parameter dictionary. You pass this dictionary as an argument in the constraint creation call. When working with metrics, each key must appear in the passed dictionary, and its value must be an NSNumber.

The two constraints in this method set the desired horizontal and vertical sizes for the view. The format strings ("H:[self(theWidth)]" and "V:[self(theHeight)]") tell the constraint system how large the view should be along each axis.

A third method, constrainPosition:, builds constraints that fix the origin of a view within its superview. Note the use of the constant to create offsets in this method.

Recipe 5-3. Basic Size Constraints


@implementation UIView (BasicConstraints)
- (void) constrainWithinSuperviewBounds
{
    if (!self.superview) return;

    // Constrain the top, bottom, left, and right to
    // within the superview's bounds
    [self.superview addConstraint:[NSLayoutConstraint
        constraintWithItem:self attribute:NSLayoutAttributeLeft
        relatedBy:NSLayoutRelationGreaterThanOrEqual
        toItem:self.superview attribute:NSLayoutAttributeLeft
        multiplier:1.0f constant:0.0f]];
    [self.superview addConstraint:[NSLayoutConstraint
        constraintWithItem:self attribute:NSLayoutAttributeTop
        relatedBy:NSLayoutRelationGreaterThanOrEqual
        toItem:self.superview attribute:NSLayoutAttributeTop
        multiplier:1.0f constant:0.0f]];
    [self.superview addConstraint:[NSLayoutConstraint
        constraintWithItem:self attribute:NSLayoutAttributeRight
        relatedBy:NSLayoutRelationLessThanOrEqual
        toItem:self.superview attribute:NSLayoutAttributeRight
        multiplier:1.0f constant:0.0f]];
    [self.superview addConstraint:[NSLayoutConstraint
        constraintWithItem:self attribute:NSLayoutAttributeBottom
        relatedBy:NSLayoutRelationLessThanOrEqual
        toItem:self.superview attribute:NSLayoutAttributeBottom
        multiplier:1.0f constant:0.0f]];
}

- (void) constrainSize:(CGSize)aSize
{
    NSMutableArray *array = [NSMutableArray array];

    // Fix the width
    [array addObjectsFromArray:[NSLayoutConstraint
        constraintsWithVisualFormat:@"H:[self(theWidth@750)]"
        options:0 metrics:@{@"theWidth":@(aSize.width)}
        views:NSDictionaryOfVariableBindings(self)]];


    // Fix the height
    [array addObjectsFromArray:[NSLayoutConstraint
        constraintsWithVisualFormat:@"V:[self(theHeight@750)]"
        options:0 metrics:@{@"theHeight":@(aSize.height)}
        views:NSDictionaryOfVariableBindings(self)]];

    [self addConstraints:array];
 }

- (void) constrainPosition: (CGPoint)aPoint
{
    if (!self.superview) return;

    NSMutableArray *array = [NSMutableArray array];

    // X position
    [array addObject:[NSLayoutConstraint constraintWithItem:self
        attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual
        toItem:self attribute:NSLayoutAttributeLeft
        multiplier:1.0f constant:aPoint.x]];

    // Y position
    [array addObject:[NSLayoutConstraint constraintWithItem:self
        attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
        toItem:self attribute:NSLayoutAttributeTop
        multiplier:1.0f constant:aPoint.y]];

    [self.superview addConstraints:array];
}
@end



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 5.


Recipe: Centering Views

To center views, associate their center properties (centerX and centerY) with the corresponding properties in their container. Recipe 5-4 introduces a pair of methods that retrieves a view’s superview and applies the equality relation between their centers.

Notice how these constraints are added to a parent view and not the child view. That’s because constraints cannot reference views outside their own subtree. Here’s the error generated if you attempt to do otherwise:

2012-06-24 16:09:14.736 HelloWorld[65437:c07] *** Terminating app due to uncaught
 exception 'NSGenericException', reason: 'Unable to install constraint on view.
Does the constraint reference something from outside the subtree of the view?
That's illegal. constraint:<NSLayoutConstraint:0x6b6ebf0 UILabel:0x6b68e40.centerY
== UIView:0x6b64a00.centerY> view:<UILabel: 0x6b68e40; frame = (0 0; 0 0); text =
'View 1'; clipsToBounds = YES; userInteractionEnabled = NO; layer = <CALayer:
0x6b67220>>'
libc++abi.dylib: terminate called throwing an exception

Here are a couple of simple rules:

• When creating constraints, add them to the superview when the superview is mentioned as either the first or second item.

• When working with format strings, add to the superview when the string contains the superview vertical pipe symbol anywhere.

Recipe 5-4. Centering Views with Constraints


@implementation UIView (BasicConstraints)
- (void) centerHorizontallyInSuperview
{
    if (!self.superview) return;

    [self.superview addConstraint:[NSLayoutConstraint
        constraintWithItem:self attribute:NSLayoutAttributeCenterX
        relatedBy:NSLayoutRelationEqual
        toItem:self.superview attribute:NSLayoutAttributeCenterX
        multiplier:1.0f constant:0.0f]];
}

- (void) centerVerticallyInSuperview
{
    if (!self.superview) return;

    [self.superview addConstraint:[NSLayoutConstraint
        constraintWithItem:self attribute:NSLayoutAttributeCenterY
        relatedBy:NSLayoutRelationEqual
        toItem:self.superview attribute:NSLayoutAttributeCenterY
        multiplier:1.0f constant:0.0f]];
}
@end



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 5.


Recipe: Setting Aspect Ratio

Constraint multipliers, the m of the y = m * x + b equation, can help set aspect ratios for your views. Recipe 5-5 demonstrates how to do this by relating a view’s height (y) to its width (x), and setting the m value to the aspect. The equation translates to view constraints through the constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant: method. Recipe 5-5 builds an NSLayoutRelationEqual relationship between the width and height of a view, using the aspect ratio as the multiplier.

The recipe applies its aspect updates by managing a fixed constraint, which it stores locally into a NSLayoutConstraint variable called aspectConstraint. Each time the user toggles the aspect from 16:9 to 4:3 or back, this recipe removes the previous constraint and creates and then stores another one. It builds this new constraint by setting the appropriate multiplier and then adds it to the view.

To allow the view’s sides some flexibility, while keeping the view reasonably large, the createLabel: method in this recipe does two things. First, it uses width and height predicates. These request that each side exceeds 300 points in length. Second, it prioritizes its requests. These priorities are high (750) but not required (1000), so the constraint system retains the power to adjust them as needed.

The outcome is a system that can change aspects in real time, and dynamically changing its layout definition at runtime.

Recipe 5-5. Creating Aspect Ratio Constraints


- (UILabel *) createLabel: (NSString *) aTitle
{
    UILabel *aLabel = [[UILabel alloc] initWithFrame:CGRectZero];
    aLabel.font = [UIFont fontWithName:@"Futura" size:24.0f];
    aLabel.textAlignment = NSTextAlignmentCenter;
    aLabel.textColor = [UIColor darkGrayColor];
    aLabel.text = aTitle;

    aLabel.translatesAutoresizingMaskIntoConstraints = NO;
    [aLabel addConstraints:[NSLayoutConstraint
        constraintsWithVisualFormat:@"H:[aLabel(>=theWidth@750)]"
        options:0 metrics:@{@"theWidth":@300.0}
        views:NSDictionaryOfVariableBindings(aLabel)]];
    [aLabel addConstraints:[NSLayoutConstraint
        constraintsWithVisualFormat:@"V:[aLabel(>=theHeight@750)]"
        options:0 metrics:@{@"theHeight":@300.0}
        views:NSDictionaryOfVariableBindings(aLabel)]];

    return aLabel;
}

- (void) toggleAspect: (id) sender
{
    // Remove any existing aspect constraint
    if (aspectConstraint)
        [self.view removeConstraint:aspectConstraint];

    // Create the new aspect constraint
    if (useFourToThree)
        aspectConstraint = [NSLayoutConstraint constraintWithItem:view1
            attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual
            toItem:view1 attribute:NSLayoutAttributeHeight
            multiplier:(4.0f / 3.0f) constant:0.0f];
    else
        aspectConstraint = [NSLayoutConstraint constraintWithItem:view1
            attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual
            toItem:view1 attribute:NSLayoutAttributeHeight
            multiplier:(16.0f / 9.0f) constant:0.0f];

    // Add it to the view
    [self.view addConstraint:aspectConstraint];
    useFourToThree = !useFourToThree;
}



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 5.


Aligning Views and Flexible Sizing

It is supremely easy to align views with constraints.

• The four format strings "H:|[self]", "H:[self]|", "V:|[self]", and "V:[self]|" respectively produce left, right, top, and bottom alignment.

• Add a predicate with a sizing relation and these format strings become stretch to left, stretch to right, and so on: "H:|[self(>0)]", "H:[self(>0)]|", "V:|[self(>0)]", and "V:[self(>0)]|".

• A second vertical pipe adds full-axis resizing, allowing views to stretch from left to right or top to bottom: "H:|[self(>0)]|" or "V:|[self(>0)]|".

• Add edge indicators to inset the stretches: "H:|-[self(>0)]-|" or "V:|-[self(>0)]-|".

Why You Cannot Distribute Views

What you cannot do, sadly, is distribute views along an axis under the current constraint system for equal spacing unless you know their positions in advance. Adding a "[A]-[B]-[C]" constraint or even a "[A]-(>=0)-[B]-(>=0)-[C]" constraint does not distribute the views along an axis with equal spacing the way you might expect. There are simply too many variables involved in setting up the spacing.

The first of these two constraint attempts ends up adding fixed insets between each view, the second adds random ones. The problem is that in no case does the first spacer have any fixed relation to the second spacer. Although I think Apple could eventually extend its system to introduce relations between spacing, it does not offer that feature in the current autolayout system.

In an imaginary world, the math for distribution would have to be the distance from A’s center to B’s center is equal to the distance from B’s center to C’s center, something like this:

Some distance such that A.center + distance = B.center and B.center + distance = C.center

There is simply no way to express that in the y R mx + b format used by the current constraint system, where the b offset must be known and relations are only between view attributes. Neither simultaneous equations

A. center == B. center + spacer1;
B. center == C. center + spacer2;
spacer1 == spacer2

nor a format string approach

[A]-spacer-[B]-spacer-[C]

are legal under the current system. Autolayout cannot handle the equations produced by this distribution. The best you can do is to calculate how far apart you want each item to be and then use a multiplier and offset to manually space out each view’s position.

If you have only three views to work with, you could pin two views to the left and right of a parent and center a third view between them. This approach breaks down when you introduce a fourth subview.

Recipe: Responding to Orientation Changes

A device’s screen geometry may influence how you want to lay out interfaces. For example, a landscape aspect ratio may not provide enough vertical range to fit in all your content. Consider Figure 5-11. The portrait layout places the iTunes album art on top of the album name, the artist name, and a Buy button with the album price. The landscape moves the album art to the left, centering it vertically, and places the album name, artist, and Buy button in the lower-right corner.

Image

Figure 5-11. Same content in portrait and landscape layouts

To accomplish this, your layout constraints must be orientation aware. View controllers provide a specific place to do this. The updateViewConstraints method enables you to change your constraints during runtime. Call the method when the device is ready to rotate but before it actually does in willRotateToInterfaceOrientation:duration:. This creates the smoothest visual update by adapting the constraints before the new orientation appears.

This recipe calls the update method before the orientation change occurs. That means the recipe must store the new orientation into a local variable (newOrientation) to know how to lay out the updated presentation. It’s a pity that Apple doesn’t offer a more orientation-aware method like updateViewConstraintsForOrientation:.

Always call the superclass implementation of updateViewConstraints before adding your own specialization code. Skipping this step provides a spectacular way to crash your app at runtime.

Recipe 5-6 uses a number of constraint macros, which are detailed in the next section.

Recipe 5-6. Updating View Constraints


- (void) updateViewConstraints
{
    [super updateViewConstraints];
    [self.view removeConstraints:self.view.constraints];

    NSDictionary *bindings = NSDictionaryOfVariableBindings(
        imageView, titleLabel, artistLabel, button);

    if (IS_IPAD ||
        UIDeviceOrientationIsPortrait(newOrientation) ||
        (newOrientation == UIDeviceOrientationUnknown))
    {
        for (UIView *view in @[imageView,
            titleLabel, artistLabel, button])
            CENTER_VIEW_H(self.view, view);
        CONSTRAIN_VIEWS(self.view, @"V:|-[imageView]-30-
            [titleLabel(>=0)]-[artistLabel]-15-[button]-(>=0)-|",
            bindings);
    }
    else
    {
        // Center image view on left
        CENTER_VIEW_V(self.view, imageView);

        // Lay out remaining views
        CONSTRAIN(self.view, imageView, @"H:|-[imageView]");
        CONSTRAIN(self.view, titleLabel, @"H:[titleLabel]-15-|");
        CONSTRAIN(self.view, artistLabel, @"H:[artistLabel]-15-|");
        CONSTRAIN(self.view, button, @"H:[button]-15-|");
        CONSTRAIN_VIEWS(self.view, @"V:|-(>=0)-[titleLabel(>=0)]
            -[artistLabel]-15-[button]-|", bindings);

        // Make sure titleLabel doesn't overlap
        CONSTRAIN_VIEWS(self.view,
            @"H:[imageView]-(>=0)-[titleLabel]", bindings);
    }
}

// Catch rotation changes
- (void) willRotateToInterfaceOrientation:
    (UIInterfaceOrientation)toInterfaceOrientation
    duration:(NSTimeInterval)duration
{
    newOrientation = toInterfaceOrientation;
    [self updateViewConstraints];
}

// Store the initial orientation
- (void) viewDidAppear:(BOOL)animated
{
    newOrientation = self.interfaceOrientation;
}



Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 5.


Constraint Macros

During my time developing this book, I have become more and more dependent on using constraints in my code. Constraints provide reliable components for view layout, and I love the way they work. That said, in their native form constraints are both overly verbose and fundamentally redundant. I end up implementing the same complex hard-to-read calls over and over again.

Debugging constraints is a real pain. Simple typos cost too much effort, and constraints tend to be the same from app to app. I soon found that a repository of predefined macros saved me time and increased the readability and reliability of my view layout sections. Instead of centering a view inside another view and having to debug that layout each time I implemented it, a single CENTER_VIEW macro does the job consistently each time.

Creating macros, as shown in Listing 5-1, shifts the work from producing exact constraint definitions to ensuring that constraints are consistent and sufficient across each entire view. These two conditions should form the focus of your view layout work

Consistent Constraints

A consistent set of constraints ensures that the iOS automatic layout system does not have to break any constraints to satisfy them all. An example of an inconsistent set of constraints might be this, using the macros in Listing 5-1:

for (UIView *subview in self.view.subviews)
    ALIGN_VIEW_TOP(self.view, subview);
CONSTRAIN_ORDER_V(self.view, view1, view2);

This snippet constrains each view to the top of its parent, and orders View 2 below View 1. These constraints are inconsistent because View 2 cannot be constrained below View 1 and aligned to the top of the parent view at the same time. One of these conditions must break.

Sufficient Constraints

A sufficient set of constraints keeps views from random placement. The following snippet defines an insufficient constraint for View 2:

ALIGN_VIEW_LEFT(self.view, view1);
ALIGN_VIEW_TOP(self.view, view1);
CONSTRAIN_ORDER_H(self.view, view1, view2);
CONSTRAIN_ORDER_V(self.view, view1, view2);

Although View 1 is pinned at the top-left of the superview, View 2 may appear at any position below and to the right of View 1 when using the flexible-order macros defined in Listing 5-1. How it is positioned depends on the whims of autolayout. Unfortunately, Apple does not yet provide a test to determine when views are under constrained and to report their specifics.

Macros

Listing 5-1 shows the current state of my macro definitions. I’ve tested these as much as time allows. They remain, however, a work in progress. Constraints are new enough that I’m still kicking the tires on my definitions and adjusting them as needed. If you find any errors, or think of any useful expansions, drop me a note and I’ll update the GitHub repository.

Listing 5-1. Constraint Macros


// Prepare Constraint Compliance
#define PREPCONSTRAINTS(VIEW)
    [VIEW setTranslatesAutoresizingMaskIntoConstraints:NO]

// Add a visual format constraint
#define CONSTRAIN(PARENT, VIEW, FORMAT)
    [PARENT addConstraints:[NSLayoutConstraint
        constraintsWithVisualFormat:(FORMAT) options:0 metrics:nil
        views:NSDictionaryOfVariableBindings(VIEW)]]
#define CONSTRAIN_VIEWS(PARENT, FORMAT, BINDINGS)
    [PARENT addConstraints:[NSLayoutConstraint  
        constraintsWithVisualFormat:(FORMAT) options:0 metrics:nil
        views:BINDINGS]]

// Stretch across axes
#define STRETCH_VIEW_H(PARENT, VIEW)
    CONSTRAIN(PARENT, VIEW, @"H:|["#VIEW"(>=0)]|")
#define STRETCH_VIEW_V(PARENT, VIEW)
    CONSTRAIN(PARENT, VIEW, @"V:|["#VIEW"(>=0)]|")
#define STRETCH_VIEW(PARENT, VIEW)
    {STRETCH_VIEW_H(PARENT, VIEW); STRETCH_VIEW_V(PARENT, VIEW);}

// Center along axes
#define CENTER_VIEW_H(PARENT, VIEW)
    [PARENT addConstraint:[NSLayoutConstraint
        constraintWithItem:VIEW attribute: NSLayoutAttributeCenterX
        relatedBy:NSLayoutRelationEqual
        toItem:PARENT attribute:NSLayoutAttributeCenterX
        multiplier:1.0f constant:0.0f]]
#define CENTER_VIEW_V(PARENT, VIEW)
    [PARENT addConstraint:[NSLayoutConstraint
        constraintWithItem:VIEW attribute: NSLayoutAttributeCenterY
        relatedBy:NSLayoutRelationEqual
        toItem:PARENT attribute:NSLayoutAttributeCenterY
        multiplier:1.0f constant:0.0f]]
#define CENTER_VIEW(PARENT, VIEW)
    {CENTER_VIEW_H(PARENT, VIEW); CENTER_VIEW_V(PARENT, VIEW);}

// Align to parent
#define ALIGN_VIEW_LEFT(PARENT, VIEW)
    [PARENT addConstraint:[NSLayoutConstraint
        constraintWithItem:VIEW attribute: NSLayoutAttributeLeft
        relatedBy:NSLayoutRelationEqual
        toItem:PARENT attribute:NSLayoutAttributeLeft
        multiplier:1.0f constant:0.0f]]
#define ALIGN_VIEW_RIGHT(PARENT, VIEW)
    [PARENT addConstraint:[NSLayoutConstraint
        constraintWithItem:VIEW attribute: NSLayoutAttributeRight
        relatedBy:NSLayoutRelationEqual
        toItem:PARENT attribute:NSLayoutAttributeRight
        multiplier:1.0f constant:0.0f]]
#define ALIGN_VIEW_TOP(PARENT, VIEW)
    [PARENT addConstraint:[NSLayoutConstraint
        constraintWithItem:VIEW attribute: NSLayoutAttributeTop
        relatedBy:NSLayoutRelationEqual
        toItem:PARENT attribute:NSLayoutAttributeTop
        multiplier:1.0f constant:0.0f]]
#define ALIGN_VIEW_BOTTOM(PARENT, VIEW)
    [PARENT addConstraint:[NSLayoutConstraint
        constraintWithItem:VIEW attribute: NSLayoutAttributeBottom
        relatedBy:NSLayoutRelationEqual
        toItem:PARENT attribute:NSLayoutAttributeBottom
        multiplier:1.0f constant:0.0f]]

// Set Size
#define CONSTRAIN_WIDTH(VIEW, WIDTH)
    CONSTRAIN(VIEW, VIEW, @"H:["#VIEW"(=="#WIDTH")]")
#define CONSTRAIN_HEIGHT(VIEW, HEIGHT)
    CONSTRAIN(VIEW, VIEW, @"V:["#VIEW"(=="#HEIGHT")]")
#define CONSTRAIN_SIZE(VIEW, HEIGHT, WIDTH)
    {CONSTRAIN_WIDTH(VIEW, WIDTH); CONSTRAIN_HEIGHT(VIEW, HEIGHT);}

// Set Aspect
#define CONSTRAIN_ASPECT(VIEW, ASPECT)
    [VIEW addConstraint:[NSLayoutConstraint
        constraintWithItem:VIEW attribute:NSLayoutAttributeWidth
        relatedBy:NSLayoutRelationEqual
        toItem:VIEW attribute:NSLayoutAttributeHeight
        multiplier:(ASPECT) constant:0.0f]]

// Item ordering
#define CONSTRAIN_ORDER_H(PARENT, VIEW1, VIEW2)
    [PARENT addConstraints: [NSLayoutConstraint
        constraintsWithVisualFormat: (@"H:["#VIEW1"]->=0-["#VIEW2"]")
        options:0 metrics:nil
        views:NSDictionaryOfVariableBindings(VIEW1, VIEW2)]]
#define CONSTRAIN_ORDER_V(PARENT, VIEW1, VIEW2)
    [PARENT addConstraints:[NSLayoutConstraint
         constraintsWithVisualFormat:(@"V:["#VIEW1"]->=0-["#VIEW2"]")
         options:0 metrics:nil
         views:NSDictionaryOfVariableBindings(VIEW1, VIEW2)]]


Debugging Your Constraints

The most common problems you encounter when adding constraints programmatically are ambiguous and unsatisfiable layouts. Expect to spend a lot of time at the Xcode debugging console, and don’t be surprised when you see a large dump of information that starts with the phrase “Unable to simultaneously satisfy constraints.”

iOS does its best during runtime to let you know which constraints could not be satisfied, and which constraints it has to break to proceed. Often, it suggests a list of constraints that you should evaluate to see which item is causing the problem.

This usually looks something like this:

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)
(
    "<NSAutoresizingMaskLayoutConstraint:0x6e5bc90 h=-&- v=-&-
UILayoutContainerView:0x6e540f0.height == UIWindow:0x6e528a0.height>",
    "<NSAutoresizingMaskLayoutConstraint:0x6e5a5e0 h=-&- v=-&-
UINavigationTransitionView:0x6e55650.height ==
UILayoutContainerView:0x6e540f0.height>",
    "<NSAutoresizingMaskLayoutConstraint:0x6e592f0 h=-&- v=-&-
UIViewControllerWrapperView:0x6bb90d0.height ==
UINavigationTransitionView:0x6e55650.height - 64>",
    "<NSAutoresizingMaskLayoutConstraint:0x6e57b90 h=-&- v=-&-
UIView:0x6baef20.height == UIViewControllerWrapperView:0x6bb90d0.height>",
    "<NSAutoresizingMaskLayoutConstraint:0x6e5cd40 h=--- v=---
V:[UIWindow:0x6e528a0(480)]>",
    "<NSAutoresizingMaskLayoutConstraint:0x6bbe890 h=--& v=--&
UILabel:0x6bb3730.midY ==>",
    "<NSLayoutConstraint:0x6bb8cc0 UILabel:0x6bb3730.centerY ==
UIView:0x6baef20.centerY>"
)

Most likely, you have forgotten to switch off translatesAutoresizingMaskIntoConstraints for one of your views. If you see an NSAutoresizingMaskLayoutConstraint listed, and it’s associated with, for example, a UILabel that you’re laying out (as is the case here), that’s a big hint about where your problem lies. I’ve highlighted the two constraints that are causing the issue.

In other cases, you might have required constraints that are simply in conflict with each other because they contradict what the other one is saying. In the following dump, I required that a view be both center-aligned and left-aligned. To keep going, the layout system had to make a choice. It decided to cancel the y-centering requirement, allowing the view to align with the top of its parent.

Will attempt to recover by breaking constraint

<NSLayoutConstraint:0x6e94250 UILabel:0x6b90e00.centerY ==
UIView:0x6b8ca50.centerY>

Although constraint dumps can be scary, certain strategies lend a hand. First, when working in code, develop your constraints a little at a time. This helps you determine when things start to break. Second, consider using the macros from Listing 5-1. There’s no reason to clutter up your code with “align with superview” and “set size to n by m” over and over again. Finally, if you’re not required to set your constraints in code, consider using the IB tools provided to make your visual layout life easier.

Summary

This chapter provided an introduction to iOS’s autolayout features. Before you move on to the next chapter, here are a few thoughts to take along with you:

• IB provides an excellent set of layout tools. However, don’t feel that you cannot create constraint-based interfaces in code. The layout system offers you excellent control over your views regardless of whether you specify your constraints visually or programmatically.

• One of the great things about working with constraints is that you move away from specific-resolution solutions for your interfaces. Yes, your user experience on a tablet is likely to be quite different from that on a member of the iPhone family, but at the same time these new tools let you design for different window (and possibly screen) sizes within the same mobile family. There’s a lot of flexibility and power hidden within these simple rules.

• Start incorporating visual ornaments like shadows into your regular design routine. Alignment rectangles ensure that your user interfaces will set up properly, regardless of any secondary view elements you add to your frames.

• Reserve visual format strings for general view layout, and use view-to-view relations for detail specifics. Both approaches play an important role, and neither should be omitted from your design playbook.

• The constraint-based autolayout system is an iOS 6-only feature. If you want to create code that supports iOS 4/5, be sure to add conditional coding that uses view autosizing instead.

• Move away from thinking in terms of struts, springs, and flexible sizes. Apple’s new layout system offers better control, with more extensible tools. I like them a lot.

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

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