As a courtesy, this is a full free rendering of my book, Programming iOS 6, by Matt Neuburg. Copyright 2013 Matt Neuburg. Please note that this edition is outdated; the current books are iOS 13 Programming Fundamentals with Swift and Programming iOS 13. If my work has been of help to you, please consider purchasing one or both of them, or you can reward me through PayPal at http://www.paypal.me/mattneub. Thank you!

Chapter 14. Views

A view (an object whose class is UIView or a subclass of UIView) knows how to draw itself into a rectangular area of the interface. Your app has a visible interface thanks to views. Creating and configuring a view can be extremely simple: “Set it and forget it.” You’ve already seen that you can drag an interface widget, such as a UIButton, into a view in the nib; when the app runs, the button appears, and works properly. But you can also manipulate views in powerful ways, in real time. Your code can do some or all of the view’s drawing of itself; it can make the view appear and disappear, move, resize itself, and display many other physical changes, possibly with animation.

A view is also a responder (UIView is a subclass of UIResponder). This means that a view is subject to user interactions, such as taps and swipes. Thus, views are the basis not only of the interface that the user sees, but also of the interface that the user touches. Organizing your views so that the correct view reacts to a given touch allows you to allocate your code neatly and efficiently.

The view hierarchy is the chief mode of view organization. A view can have subviews; a subview has exactly one immediate superview. Thus there is a tree of views. This hierarchy allows views to come and go together. If a view is removed from the interface, its subviews are removed; if a view is hidden (made invisible), its subviews are hidden; if a view is moved, its subviews move with it; and other changes in a view are likewise shared with its subviews. The view hierarchy is also the basis of, though it is not identical to, the responder chain (Chapter 11).

A view may come from a nib, or you can create it in code. On balance, neither approach is to be preferred over the other; it depends on your needs and inclinations and on the overall architecture of your app.

The Window

The top of the view hierarchy is the app’s window. It is an instance of UIWindow (or your own subclass thereof), which is a UIView subclass. Your app should have exactly one main window. It occupies the entire screen and forms the background to, and is the ultimate superview of, all your other visible views. Other views are visible by virtue of being subviews, at some depth, of your app’s window. (If your app can display views on an external screen, you’ll create an additional UIWindow to contain those views; but in this chapter I’ll behave as if there were just one screen, the device’s own screen, and just one window.)

The Xcode project templates all generate your app’s window for you. The technique used, in a nonstoryboard app, is to create the window explicitly in code, in the app delegate’s application:didFinishLaunchingWithOptions:. The window must persist for the lifetime of the app, so the app delegate has a window property that retains it:

self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

The window’s designated initializer is initWithFrame:; I’ll explain in a moment what “frame” and “bounds” are, but the effect is to make the window the same size as the screen. In the template, the comment, “Override point for customization after application launch,” comes after that line of code, because any code customizing what’s in the window will need the window to exist first. The various templates adopt various strategies for giving the window some content; this generally involves setting the window’s rootViewController to a UIViewController, whose view thus automatically becomes the window’s single primary subview. (I will refer to this as the window’s root view.)

For example, in the Single View Application project template, the ViewController class is instantiated, and the resulting instance is set to the window’s rootViewController. This sets in motion a further train of events automatically. The nib ViewController.xib loads with the ViewController instance as its owner; the UIView pointed to by the File’s Owner’s view outlet in the nib is instantiated, and the ViewController instance’s view property is set by the nib-loading process to that UIView; and the window makes that view its single primary subview. Finally, the template code sends the window instance the makeKeyAndVisible message in order to make your app’s interface appear.

If you choose the Storyboard option as you specify a template, the process works a little differently. The app is given a main storyboard, pointed to by the Info.plist key “Main storyboard file base name” (UIMainStoryboardFile). After UIApplicationMain instantiates the app delegate class (Chapter 6), it asks the app delegate for the value of its window property; if that value is nil, the window is created and assigned to the app delegate’s window property. The storyboard’s initial view controller is then instantiated and assigned to the window’s rootViewController property, with the result that its view is placed in the window as its root view; the window is then sent the makeKeyAndVisible message. All of that is done behind the scenes by UIApplicationMain, with no visible code whatever. That is why, in a storyboard template, the application:didFinishLaunchingWithOptions: implementation is empty.

An app whose main window has no rootViewController is not strictly illegal, but it is strongly discouraged by a warning from the runtime as the app launches. The Single View Application template supplies a minimal root view controller for you. The Empty Application template does not; if you use it as is, it generates the warning. To experiment with code that populates the interface with views, you can use the Empty Application template and put your code in its application:didFinishLaunchingWithOptions:, but if you want to avoid the warning, you’ll need to create a minimal root view controller and add the views to its view, like this:

self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.rootViewController = [UIViewController new];
UIView* v = [[UIView alloc] initWithFrame:CGRectMake(100,100,50,50)];
v.backgroundColor = [UIColor redColor]; // small red square
// add it to the root view controller's view
[self.window.rootViewController.view addSubview: v];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;

Alternatively, use the Single View Application template. In that case, any code that adds views should appear in View Controller’s viewDidLoad implementation, and the views should be added to self.view:

[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIView* v = [[UIView alloc] initWithFrame:CGRectMake(100,100,50,50)];
v.backgroundColor = [UIColor redColor]; // small red square
// add it to this view controller's view
[self.view addSubview: v];

To experiment with views created in a nib, start with the Single View Application project template, as we did with our earlier Empty Window example. The view supplied in the nib will become the window’s root view, and whatever you drag into it in the nib will appear in the window when the app runs.

It is improbable that you would want to subclass UIWindow and substitute an instance of your subclass as the app’s main window, but you can certainly do so. If the window is generated explicitly in code, you would obviously substitute the name of your window subclass as the class to be instantiated and assigned to the app delegate’s window property in application:didFinishLaunchingWithOptions:, in the template code I quoted a moment ago. If you’re using a main storyboard, however, application:didFinishLaunchingWithOptions: is too late; you’ll have to perform the substitution when UIApplicationMain asks for the app delegate’s window property, by implementing the app delegate’s window getter to create the window and set the window property exactly once:

- (UIWindow*) window {
    UIWindow* w = self->_window;
    if (!w) {
        w = [[MyWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
        self->_window = w;
    }
    return w;
}

Once the app is up and running, the app delegate points to the window as the value of its window property; so any code in the app delegate class can refer to the window as self.window. Code elsewhere can get a reference to the app delegate, so it can also get a reference to the app’s window:

UIWindow* theWindow = [[[UIApplication sharedApplication] delegate] window];

That code is unusual, though, and may require typecasting to quiet the compiler (because the class of the application’s delegate property is otherwise unknown). You’d be more likely to use the application’s keyWindow property:

UIWindow* theWindow = [[UIApplication sharedApplication] keyWindow];

Perhaps the most typical way to get a reference to your app’s window would be through a subview of the window, at any depth of the hierarchy. You are very likely to have a reference to at least one such subview, and its window property points to the window that contains it, which is the app’s window. You can also use a UIView’s window property as a way of asking whether it is ultimately embedded in a window; if it isn’t, its window property is nil. A UIView whose window property is nil cannot be visible to the user.

Although your app will have exactly one primary window, it may generate other windows of which you are not conscious. For example, if you put up an alert view (UIAlertView), it is displayed in a secondary window that lies on top of your app’s window; at that moment, this secondary window is the application’s keyWindow. You would not be conscious of this fact, however, unless you needed a reference to your app’s window while an alert was showing, which is unlikely.

The window’s backgroundColor property, which it inherits from UIView, affects the appearance of the app if the window is visible behind its subviews. However, you are likely to give your window a primary subview that occupies the entire window and blocks it from sight; the window’s backgroundColor would then make no visible difference. The window would function solely as a container for the app’s visible views.

Subview and Superview

Once upon a time, and not so very long ago, a view owned precisely its rectangular area. No part of any view that was not a subview of this view could appear inside it, because when this view redrew its rectangle, it would erase the overlapping portion of the other view. No part of any subview of this view could appear outside it, because the view took responsibility for its own rectangle and no more.

Those rules, however, were gradually relaxed, and starting in Mac OS X 10.5 Apple introduced an entirely new architecture for view drawing that lifted those restrictions completely. iOS view drawing is based on this revised architecture. So now some or all of a subview can appear outside its superview, and a view can overlap another view and can be drawn partially or totally in front of it without being its subview.

So, for example, Figure 14.1 shows three overlapping views. All three views have a background color, so each is completely represented by a colored rectangle. You have no way of knowing, from this visual representation, exactly how the views are related within the view hierarchy. In actual fact, the view in the middle (horizontally) is a sibling view of the view on the left (they are both direct subviews of the root view), and the view on the right is a subview of the middle view.

figs/pios_1401.png

Figure 14.1. Overlapping views


When views are created in the nib, you can examine the view hierarchy in the expanded dock to learn their actual relationship (Figure 14.2). When views are created in code, you know their hierarchical relationship because you created that hierarchy. But the visible interface doesn’t tell you, because view overlapping is so flexible.

figs/pios_1402.png

Figure 14.2. A view hierarchy as displayed in the nib


Nevertheless, a view’s position in the view hierarchy does affect how it is drawn. Most important, a view’s position in the view hierarchy dictates the order in which it is drawn. Sibling subviews of the same superview have a layering order: one is “further back” than the other. This will make no visible difference if there is no overlap, but the subview that is “further back” is drawn first, so if there is overlap, it will appear to be behind its sibling. Similarly, a superview is “further back” than its subviews; the superview is drawn first, so it will appear to be behind its subviews.

You can see this illustrated in Figure 14.1. The view on the right is a subview of the view in the middle and is drawn on top of it. The view on the left is a sibling of the view in the middle, but it is a later sibling, so it is drawn on top of the view in the middle and on top of the view on the right. The view on the left cannot appear behind the view on the right but in front of the view in the middle, because those views are subview and superview and are drawn together — both are drawn either before or after the view on the left, depending on the “further back” ordering of the siblings.

This layering order can be governed in the nib by arranging the views in the expanded dock. (If you click in the canvas, you may be able to use the menu items of the Editor → Arrange menu instead — Send to Front, Send to Back, Send Forward, Send Backward.) In code, there are methods for arranging the sibling order of views, which we’ll come to in a moment.

Here are some other effects of the view hierarchy:

  • If a view is removed from or moved within its superview, its subviews go with it.
  • If a view’s size is changed, its subviews can be resized automatically.
  • A view’s degree of transparency is inherited by its subviews.
  • A view can optionally limit the drawing of its subviews so that any parts of them outside the view are not shown. This is called clipping and is set with the view’s clipsToBounds property.
  • A superview owns its subviews, in the memory-management sense, much as an NSArray owns its elements; it retains them and is responsible for releasing a subview when that subview ceases to be its subview (it is removed from the collection of this view’s subviews) or when it itself goes out of existence.

A UIView has a superview property (a UIView) and a subviews property (an NSArray of UIViews, in back-to-front order), allowing you to trace the view hierarchy in code. There is also a method isDescendantOfView: letting you check whether one view is a subview of another at any depth. If you need a reference to a particular view, you will probably arrange this beforehand as an instance variable, perhaps through an outlet. Alternatively, a view can have a numeric tag (its tag property), and can then be referred to by sending any view higher up the view hierarchy the viewWithTag: message. Seeing that all tags of interest are unique within their region of the hierarchy is up to you.

Manipulating the view hierarchy in code is easy. This is part of what gives iOS apps their dynamic quality, and it compensates for the fact that there is basically just a single window. It is perfectly reasonable for your code to rip an entire hierarchy of views out of the superview and substitute another. Such behavior can be implemented elegantly by using a UIViewController, a subject to which we’ll return later (Chapter 19). But you can do it directly, too. The method addSubview: makes one view a subview of another; removeFromSuperview takes a subview out of its superview’s view hierarchy. In both cases, if the superview is part of the visible interface, the subview will appear or disappear; and of course this view may itself have subviews that accompany it. Just remember that removing a subview from its superview releases it; if you intend to reuse that subview later on, you will wish to retain it first. This is often taken care of through a property with a retain policy.

Events inform a view of these dynamic changes. To respond to these events requires subclassing. Then you’ll be able to override any of didAddSubview: and willRemoveSubview:, didMoveToSuperview and willMoveToSuperview:, didMoveToWindow and willMoveToWindow:.

When addSubview: is called, the view is placed last among its superview’s subviews; thus it is drawn last, meaning that it appears frontmost. A view’s subviews are indexed, starting at 0, which is rearmost. There are additional methods for inserting a subview at a given index (insertSubview:atIndex:), or below (behind) or above (in front of) a specific view (insertSubview:belowSubview:, insertSubview:aboveSubview:); for swapping two sibling views by index (exchangeSubviewAtIndex:withSubviewAtIndex:); and for moving a subview all the way to the front or back among its siblings (bringSubviewToFront:, sendSubviewToBack:).

Oddly, there is no command for removing all of a view’s subviews at once. However, a view’s subviews array is an immutable copy of the internal list of subviews, so it is legal to cycle through it and remove each subview one at a time:

for (UIView* v in view.subviews)
    [v removeFromSuperview];

Visibility and Opacity

A view can be made invisible by setting its hidden property to YES, and visible again by setting it to NO. This takes it (and its subviews, of course) out of the visible interface without the overhead of actually removing it from the view hierarchy. A hidden view does not (normally) receive touch events, so to the user it really is as if the view weren’t there. But it is there, so it can still be manipulated in code.

A view can be assigned a background color through its backgroundColor property. A color is a UIColor; this is not a difficult class to use, and I’m not going to go into details. A view whose background color is nil (the default) has a transparent background. It is perfectly reasonable for a view to have a transparent background and to do no additional drawing of its own, just so that it can act as a convenient superview to other views, making them behave together.

A view can be made partially or completely transparent through its alpha property: 1.0 means opaque, 0.0 means transparent, and a value may be anywhere between them, inclusive. This affects subviews: if a superview has an alpha of 0.5, none of its subviews can have an apparent opacity of more than 0.5, because whatever alpha value they have will be drawn relative to 0.5. (Just to make matters more complicated, colors have an alpha value as well. So, for example, a view can have an alpha of 1.0 but still have a transparent background because its backgroundColor has an alpha less than 1.0.) A view that is completely transparent (or very close to it) is like a view whose hidden is YES: it is invisible, along with its subviews, and cannot (normally) be touched.

A view’s alpha property value affects the apparent transparency of its background color and the apparent transparency of its contents separately. For example, if a view displays an image and has a background color and its alpha is less than 1, the background color will seep through the image (and whatever is behind the view will seep through both).

A view’s opaque property, on the other hand, is a horse of a different color; changing it has no effect on the view’s appearance. Rather, this property is a hint to the drawing system. If a view completely fills its bounds with ultimately opaque material and its alpha is 1.0, so that the view has no effective transparency, then it can be drawn more efficiently (with less drag on performance) if you inform the drawing system of this fact by setting its opaque to YES. Otherwise, you should set its opaque to NO. The opaque value is not changed for you when you set a view’s backgroundColor or alpha! Setting it correctly is entirely up to you; the default, perhaps surprisingly, is YES.

Frame

A view’s frame property, a CGRect, is the position of its rectangle within its superview, in the superview’s coordinate system. By default, the superview’s coordinate system will have the origin at its top left, with the x-coordinate growing positively rightward and the y-coordinate growing positively downward.

Setting a view’s frame to a different CGRect value repositions the view, or resizes it, or both. If the view is visible, this change will be visibly reflected in the interface. On the other hand, you can also set a view’s frame when the view is not visible — for example, when you create the view in code. In that case, the frame describes where the view will be positioned within its superview when it is given a superview. UIView’s designated initializer is initWithFrame:, and you’ll often assign a frame this way, especially because the default frame might otherwise be {{0,0},{0,0}}, which is rarely what you want.

Note

Forgetting to assign a view a frame when creating it in code, and then wondering why it isn’t appearing when added to a superview, is a common beginner mistake. A view with a zero-size frame is effectively invisible. If a view has a standard size that you want it to adopt, especially in relation to its contents (like a UIButton in relation to its title), an alternative is to send it the sizeToFit message.

Knowing this, we can generate programmatically the interface displayed in Figure 14.1. This code might appear in the application:didFinishLaunchingWithOptions: method of the app delegate in an Empty Application template-based app (as I suggested earlier):

UIView* v1 = [[UIView alloc] initWithFrame:CGRectMake(113, 111, 132, 194)];
v1.backgroundColor = [UIColor colorWithRed:1 green:.4 blue:1 alpha:1];
UIView* v2 = [[UIView alloc] initWithFrame:CGRectMake(41, 56, 132, 194)];
v2.backgroundColor = [UIColor colorWithRed:.5 green:1 blue:0 alpha:1];
UIView* v3 = [[UIView alloc] initWithFrame:CGRectMake(43, 197, 160, 230)];
v3.backgroundColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:1];
[self.window.rootViewController.view addSubview: v1];
[v1 addSubview: v2];
[self.window.rootViewController.view addSubview: v3];

In that code, we determined the layering order of v1 and v3 (the middle and left views, which are sibling subviews of the window) by the order in which we inserted them into the view hierarchy with addSubview:.

Bounds and Center

Suppose we wish to give a view a subview inset by 10 points, as in Figure 14.3. The utility function CGRectInset makes it easy to derive one rectangle as an inset from another, but what rectangle should we use as a basis? Not the superview’s frame; the frame represents a view’s position within its superview, and in that superview’s coordinates. What we’re after is a CGRect describing our superview’s rectangle in its own coordinates, because those are the coordinates in which the subview’s frame is to be expressed. That CGRect, describing a view’s rectangle in its own coordinates, is the view’s bounds property.

figs/pios_1403.png

Figure 14.3. A subview inset from its superview


So, the code to generate Figure 14.3 looks like this:

UIView* v1 = [[UIView alloc] initWithFrame:CGRectMake(113, 111, 132, 194)];
v1.backgroundColor = [UIColor colorWithRed:1 green:.4 blue:1 alpha:1];
UIView* v2 = [[UIView alloc] initWithFrame:CGRectInset(v1.bounds, 10, 10)];
v2.backgroundColor = [UIColor colorWithRed:.5 green:1 blue:0 alpha:1];
[self.window.rootViewController.view addSubview: v1];
[v1 addSubview: v2];

You’ll very often use a view’s bounds in this way. When you need coordinates for drawing inside a view, whether drawing manually or placing a subview, you’ll often refer to the view’s bounds.

Interesting things happen when you set a view’s bounds. If you change a view’s bounds size, you change its frame. The change in the view’s frame takes place around its center, which remains unchanged. So, for example:

UIView* v1 = [[UIView alloc] initWithFrame:CGRectMake(113, 111, 132, 194)];
v1.backgroundColor = [UIColor colorWithRed:1 green:.4 blue:1 alpha:1];
UIView* v2 = [[UIView alloc] initWithFrame:CGRectInset(v1.bounds, 10, 10)];
v2.backgroundColor = [UIColor colorWithRed:.5 green:1 blue:0 alpha:1];
[self.window.rootViewController.view addSubview: v1];
[v1 addSubview: v2];
CGRect f = v2.bounds;
f.size.height += 20;
f.size.width += 20;
v2.bounds = f;

What appears is a single rectangle; the subview completely and exactly covers its superview, its frame being the same as the superview’s bounds. The call to CGRectInset started with the superview’s bounds and shaved 10 points off the left, right, top, and bottom to set the subview’s frame (Figure 14.3). But then we added 20 points to the subview’s bounds height and width, and thus added 20 points to the subview’s frame height and width as well (Figure 14.4). The center didn’t move, so we effectively put the 10 points back onto the left, right, top, and bottom of the subview’s frame.

figs/pios_1404.png

Figure 14.4. A subview exactly covering its superview


When you create a UIView, its bounds coordinate system’s {0,0} point is at its top left. If you change a view’s bounds origin, you move the origin of its internal coordinate system. Because a subview is positioned in its superview with respect to its superview’s coordinate system, a change in the bounds origin of the superview will change the apparent position of a subview. To illustrate, we start with our subview inset evenly within its superview, and then change the bounds origin of the superview:

UIView* v1 = [[UIView alloc] initWithFrame:CGRectMake(113, 111, 132, 194)];
v1.backgroundColor = [UIColor colorWithRed:1 green:.4 blue:1 alpha:1];
UIView* v2 = [[UIView alloc] initWithFrame:CGRectInset(v1.bounds, 10, 10)];
v2.backgroundColor = [UIColor colorWithRed:.5 green:1 blue:0 alpha:1];
[self.window.rootViewController.view addSubview: v1];
[v1 addSubview: v2];
CGRect f = v1.bounds;
f.origin.x += 10;
f.origin.y += 10;
v1.bounds = f;

Nothing happens to the superview’s size or position. But the subview has moved up and to the left so that it is flush with its superview’s top-left corner (Figure 14.5). Basically, what we’ve done is to say to the superview, “Instead of calling the point at your upper left {0,0}, call that point {10,10}.” Because the subview’s frame origin is itself at {10,10}, the subview now touches the superview’s top-left corner. The effect of changing a view’s bounds origin may seem directionally backward — we increased the superview’s origin in the positive direction, but the subview moved in the negative direction — but think of it this way: a view’s bounds origin point coincides with its frame’s top left.

figs/pios_1405.png

Figure 14.5. The superview’s bounds origin has been shifted


We have seen that changing a view’s bounds size affects its frame size. The converse is also true: changing a view’s frame size affects its bounds size. What is not affected by changing a view’s bounds size is the view’s center. This property, like the frame property, represents the view’s position within its superview, in the superview’s coordinates, but it is the position of the bounds center, the point derived from the bounds like this:

CGPoint c = CGPointMake(CGRectGetMidX(theView.bounds),
                        CGRectGetMidY(theView.bounds));

A view’s center is thus a single point establishing the positional relationship between a view’s bounds and its superview’s bounds. Changing a view’s bounds does not change its center (we already saw that when we increased a view’s bounds size, its frame expanded around a stationary center); changing a view’s center does not change its bounds.

Thus, a view’s bounds and center are orthogonal (independent), and describe (among other things) both the view’s size and its position within its superview. The view’s frame is therefore superfluous! In fact, the frame property is merely a convenient expression of the center and bounds values. In most cases, this won’t matter to you; you’ll use the frame property anyway. When you first create a view from scratch, the designated initializer is initWithFrame:. You can change the frame, and the bounds size and center will change to match. You can change the bounds size or the center, and the frame will change to match. Nevertheless, the proper and most reliable way to position and size a view within its superview is to use its bounds and center, not its frame; there are some situations in which the frame is meaningless (or will at least behave very oddly), but the bounds and center will always work.

We have seen that every view has its own coordinate system, expressed by its bounds, and that a view’s coordinate system has a clear relationship to its superview’s coordinate system, expressed by its center. This is true of every view in a window, so it is possible to convert between the coordinates of any two views in the same window. Convenience methods are supplied to perform this conversion both for a CGPoint and for a CGRect: convertPoint:fromView:, convertPoint:toView:, convertRect:fromView:, and convertRect:toView:. If the second parameter is nil, it is taken to be the window.

For example, if v2 is a subview of v1, then to center v2 within v1 you could say:

v2.center = [v1 convertPoint:v1.center fromView:v1.superview];

Warning

When setting a view’s position by setting its center, if the height or width of the view is not an even integer, the view can end up misaligned (on a single-resolution screen): its point values in one or both dimensions are located between the screen pixels. This can cause the view to be displayed incorrectly; for example, if the view contains text, the text may be blurry. You can detect this situation in the Simulator by checking Debug → Color Misaligned Images. A simple solution is to set the view’s frame, after positioning it, to the CGRectIntegral of its frame.

Transform

A view’s transform property alters how the view is drawn — it may, for example, change the view’s perceived size and orientation — without affecting its bounds and center. A transformed view continues to behave correctly: a rotated button, for example, is still a button, and can be tapped in its apparent location and orientation.

A transform value is a CGAffineTransform, which is a struct representing six of the nine values of a 3×3 transformation matrix (the other three values are constants, so there’s no point representing them in the struct). You may have forgotten your high-school linear algebra, so you may not recall what a transformation matrix is. For the details, which are quite simple really, see the “Transforms” chapter of Apple’s Quartz 2D Programming Guide, especially the section called “The Math Behind the Matrices.” But you don’t really need to know those details, because convenience functions, whose names start with CGAffineTransformMake..., are provided for creating three of the basic types of transform: rotation, scaling, and translation (i.e., changing the view’s apparent position). A fourth basic transform type, skewing or shearing, has no convenience function.

By default, a view’s transformation matrix is CGAffineTransformIdentity, the identity transform. It has no visible effect, so you’re unaware of it. Any transform that you do apply takes place around the view’s center, which is held constant.

Here’s some code to illustrate use of a transform:

UIView* v1 = [[UIView alloc] initWithFrame:CGRectMake(113, 111, 132, 194)];
v1.backgroundColor = [UIColor colorWithRed:1 green:.4 blue:1 alpha:1];
UIView* v2 = [[UIView alloc] initWithFrame:CGRectInset(v1.bounds, 10, 10)];
v2.backgroundColor = [UIColor colorWithRed:.5 green:1 blue:0 alpha:1];
[self.window.rootViewController.view addSubview: v1];
[v1 addSubview: v2];
v1.transform = CGAffineTransformMakeRotation(45 * M_PI/180.0);

The transform property of the view v1 is set to a rotation transform. The result (Figure 14.6) is that the view appears to be rocked 45 degrees clockwise. (I think in degrees, but Core Graphics thinks in radians, so my code has to convert.) Observe that the view’s center property is unaffected, so that the rotation seems to have occurred around the view’s center. Moreover, the view’s bounds property is unaffected; the internal coordinate system is unchanged, so the subview is drawn in the same place relative to its superview. The view’s frame, however, is now useless, as no mere rectangle can describe the region of the superview apparently occupied by the view; the frame’s actual value, {{63.7416, 92.7416}, {230.517, 230.517}}, describes the minimal bounding rectangle surrounding the view’s apparent position. The rule is that if a view’s transform is not the identity transform, you should not set its frame; also, automatic resizing of a subview, discussed later in this chapter, requires that the superview’s transform be the identity transform.

figs/pios_1406.png

Figure 14.6. A rotation transform


Suppose, instead of CGAffineTransformMakeRotation, we call CGAffineTransformMakeScale, like this:

v1.transform = CGAffineTransformMakeScale(1.8, 1);

The bounds property of the view v1 is still unaffected, so the subview is still drawn in the same place relative to its superview; this means that the two views seem to have stretched horizontally together (Figure 14.7). No bounds or centers were harmed by the application of this transform!

figs/pios_1407.png

Figure 14.7. A scale transform


Transformation matrices can be chained. There are convenience functions for applying one transform to another. Their names do not contain “Make.” These functions are not commutative; that is, order matters. If you start with a transform that translates a view to the right and then apply a rotation of 45 degrees, the rotated view appears to the right of its original position; on the other hand, if you start with a transform that rotates a view 45 degrees and then apply a translation to the right, the meaning of “right” has changed, so the rotated view appears 45 degrees down from its original position. To demonstrate the difference, I’ll start with a subview that exactly overlaps its superview:

UIView* v1 = [[UIView alloc] initWithFrame:CGRectMake(20, 111, 132, 194)];
v1.backgroundColor = [UIColor colorWithRed:1 green:.4 blue:1 alpha:1];
UIView* v2 = [[UIView alloc] initWithFrame:v1.bounds];
v2.backgroundColor = [UIColor colorWithRed:.5 green:1 blue:0 alpha:1];
[self.window.rootViewController.view addSubview: v1];
[v1 addSubview: v2];

Then I’ll apply two successive transforms to the subview, leaving the superview to show where the subview was originally. In this example, I translate and then rotate (Figure 14.8):

v2.transform = CGAffineTransformMakeTranslation(100, 0);
v2.transform = CGAffineTransformRotate(v2.transform, 45 * M_PI/180.0);
figs/pios_1408.png

Figure 14.8. Translation, then rotation


In this example, I rotate and then translate (Figure 14.9):

v2.transform = CGAffineTransformMakeRotation(45 * M_PI/180.0);
v2.transform = CGAffineTransformTranslate(v2.transform, 100, 0);
figs/pios_1409.png

Figure 14.9. Rotation, then translation


The function CGAffineTransformConcat concatenates two transform matrices using matrix multiplication. Again, this operation is not commutative. The order is the opposite of the order when using convenience functions for applying one transform to another. For example, this gives the same result as Figure 14.9:

CGAffineTransform r = CGAffineTransformMakeRotation(45 * M_PI/180.0);
CGAffineTransform t = CGAffineTransformMakeTranslation(100, 0);
v2.transform = CGAffineTransformConcat(t,r); // not r,t

To remove a transform from a combination of transforms, apply its inverse. A convenience function lets you obtain the inverse of a given affine transform. Again, order matters. In this example, I rotate the subview and shift it to its “right,” and then remove the rotation (Figure 14.10):

CGAffineTransform r = CGAffineTransformMakeRotation(45 * M_PI/180.0);
CGAffineTransform t = CGAffineTransformMakeTranslation(100, 0);
v2.transform = CGAffineTransformConcat(t,r);
v2.transform =
    CGAffineTransformConcat(CGAffineTransformInvert(r), v2.transform);
figs/pios_1410.png

Figure 14.10. Rotation, then translation, then inversion of the rotation


Finally, as there are no convenience methods for creating a skew (shear) transform, I’ll illustrate by creating one manually, without further explanation (Figure 14.11):

v1.transform = CGAffineTransformMake(1, 0, -0.2, 1, 0, 0);
figs/pios_1411.png

Figure 14.11. Skew (shear)


Transforms are useful particularly as temporary visual indicators. For example, you might call attention to a view by applying a transform that scales it up slightly, and then applying the identity transform to restore it to its original size, and animating those changes (Chapter 17).

The transform property lies at the heart of an iOS app’s ability to rotate its interface. The window’s frame and bounds, as I’ve already said, are invariant, locked to the screen; but the root view’s frame and bounds are not. Suppose the user rotates the device 90 degrees and the app interface is to rotate to compensate. How is this done? The root view’s frame is adjusted to match the new applicationFrame, so that it continues to fill the window except for the part covered by the status bar. In addition, a 90-degree rotation transform is applied to the root view, so that its {0,0} point moves to what the user now sees as the top left of the view. The root view’s subviews have their frame in the root view’s bounds coordinate system, so they are effectively rotated.

But what about the position of the root view’s subviews? Consider, for example, a subview located at the bottom right of the screen when the device is in portrait orientation. If the device is rotated 90 degrees, the screen is now considerably shorter vertically, and if a 90-degree rotation transform is applied to the root view to compensate for the device rotation, the root view’s bounds width and bounds height are effectively swapped, with what was the longer dimension becoming the shorter dimension and vice versa. So that poor old subview will now be off the screen — unless something further is done. That’s the subject of the next section.

Layout

We have seen that a subview moves when its superview’s bounds origin is changed. But what happens to a subview when its superview’s bounds size is changed? (And remember, this includes changing the superview’s frame size.)

Of its own accord, nothing happens. The subview’s bounds and center haven’t changed, and the superview’s bounds origin hasn’t moved, so the subview stays in the same position relative to the top left of its superview. In real life, however, that often won’t be what you want. You’ll want subviews to be resized and repositioned when their superview’s bounds size is changed. This is called layout.

The need for layout is obvious in a context such as Mac OS X, where the user can freely resize a window, potentially disturbing your interface. For example, you’d want an OK button near the lower-right corner to stay in the lower-right corner as the window grows, while a text field at the top of the window should stay at the top of the window, but perhaps should widen as the window widens.

There are no user-resizable windows on an iOS device, but still, a superview might be resized dynamically. For example, you might respond to the user rotating the device 90 degrees by swapping the width and height values of a view; now its subviews should shift to compensate. Or you might want to provide a reusable complex view, such as a table view cell containing several subviews, without knowing its precise final dimensions in advance. And the introduction of the iPhone 5, with its taller screen, means that one and the same app might launch into portrait orientation on an iPhone 4 and on an iPhone 5, with different height dimensions, which may call for some adjustment of the interface.

Layout is performed in three primary ways in iOS 6:

Manual layout
The superview is sent the layoutSubviews message whenever it is resized; so, to lay out subviews manually, provide your own subclass and override layoutSubviews. Clearly this could turn out to be a lot of work, but it means you can do anything you like.
Autoresizing
Autoresizing is the pre-iOS 6 way of performing layout automatically. It depends ultimately on the superview’s autoresizesSubviews property. To turn off a view’s automatic resizing altogether, set this property to NO. If it is YES, then a subview will respond automatically to its superview’s being resized, in accordance with the rules prescribed by the subview’s autoresizingMask property value. Autoresizing is performed before layoutSubviews is called.
Autolayout

Autolayout, introduced in iOS 6, depends on the constraints of views. A constraint (an instance of NSLayoutConstraint) is much more sophisticated than the autoresizingMask; it’s a full-fledged object with numeric values, and can describe a relationship between any two views (not just a subview and its superview).

Autolayout is an opt-in technology (though it is the default for new nibs created in Xcode 4.5 and later), and is incompatible with systems before iOS 6. You can implement it on a view by view basis; for example, you might lay out a superview using autoresizing but its subviews using autolayout. Autolayout replaces not only autoresizing but also the use of a view’s frame (or bounds and center) to position that view; once you choose to use autolayout for a view, you should use only autolayout for that view, both to set its position and to determine what should happen if there’s a change in another view to which this one is bound by a constraint.

Autoresizing

Autoresizing is a matter of conceptually assigning a subview “springs and struts.” A spring can stretch; a strut can’t. Springs and struts can be assigned internally or externally. Thus you can specify (using internal springs and struts) whether and how the view can be resized, and (using external springs and struts) whether and how the view can be repositioned. For example:

  • Imagine a subview that is centered in its superview and is to stay centered, but is to resize itself as the superview is resized. It would have struts externally and springs internally.
  • Imagine a subview that is centered in its superview and is to stay centered, and is not to resize itself as the superview is resized. It would have springs externally and struts internally.
  • Imagine an OK button that is to stay in the lower right of its superview. It would have struts internally, struts externally to its right and bottom, and springs externally to its top and left.
  • Imagine a text field that is to stay at the top of its superview. It is to widen as the superview widens. It would have struts externally; internally it would have a vertical strut and a horizontal spring.

To experiment with autoresizing in a nib, you’ll need autolayout to be turned off for that nib. By default, autolayout is turned on for a new nib created in Xcode 4.5 and later. To turn it off, select the nib in the Project navigator and show the File inspector: uncheck “Use autolayout.”

When editing a nib file with autolayout turned off, you can assign a view springs and struts in the Size inspector (Autosizing). A solid line externally represents a strut; a solid line internally represents a spring. A helpful animation shows you the effect on your view’s position as its superview is resized.

In code, a combination of springs and struts is set through a view’s autoresizingMask property. It’s a bitmask, so you use logical-or to combine options (Chapter 1). The options, with names that start with UIViewAutoresizingFlexible..., represent springs; whatever isn’t specified is a strut. The default is UIViewAutoresizingNone, meaning all struts — but of course it can’t really be all struts, because if the superview is resized, something needs to change; in reality, UIViewAutoresizingNone is the same as UIViewAutoresizingFlexibleRightMargin together with UIViewAutoresizingFlexibleBottomMargin.

To demonstrate autoresizing, I’ll start with a view and two subviews, one stretched across the top, the other confined to the lower right (Figure 14.12):

UIView* v1 = [[UIView alloc] initWithFrame:CGRectMake(100, 111, 132, 194)];
v1.backgroundColor = [UIColor colorWithRed:1 green:.4 blue:1 alpha:1];
UIView* v2 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 132, 10)];
v2.backgroundColor = [UIColor colorWithRed:.5 green:1 blue:0 alpha:1];
UIView* v3 = [[UIView alloc] initWithFrame:CGRectMake(v1.bounds.size.width-20,
                                                      v1.bounds.size.height-20,
                                                      20, 20)];
v3.backgroundColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:1];
[self.window.rootViewController.view addSubview: v1];
[v1 addSubview: v2];
[v1 addSubview: v3];
figs/pios_1412.png

Figure 14.12. Before autoresizing


To that example, I’ll add code applying springs and struts to the two subviews to make them behave like the text field and the OK button I was hypothesizing earlier:

v2.autoresizingMask = UIViewAutoresizingFlexibleWidth;
v3.autoresizingMask = UIViewAutoresizingFlexibleTopMargin |
                      UIViewAutoresizingFlexibleLeftMargin;

Now I’ll resize the superview, thus bringing autoresizing into play; as you can see (Figure 14.13), the subviews remain pinned in their correct relative positions:

CGRect f = v1.bounds;
f.size.width += 40;
f.size.height -= 50;
v1.bounds = f;
figs/pios_1413.png

Figure 14.13. After autoresizing


Autolayout

Autolayout is an opt-in technology. By default, as your app launches, autolayout is switched off, and the system behaves as in iOS 5 and before. But if, at any time while your app runs, the system sees an autolayout constraint (generated in code or by the loading of a nib that has “Use autolayout” checked), the autolayout system is switched on, and from then on you’re running under autolayout.

Note

A common place to create constraints is in a view’s updateConstraints implementation (discussed later in this chapter). However, if there are no constraints to start with, updateConstraints won’t be called. So you might need a way to bootstrap autolayout. That way is to implement the UIView class method requiresConstraintBasedLayout to return YES in some UIView subclass.

An autolayout constraint, or simply constraint, is an NSLayoutConstraint instance, and describes either the absolute width or height of a view or a relationship between an attribute of one view and an attribute of another view. In the latter case, the attributes don’t have to be the same attribute, and the two views don’t have to be siblings (subviews of the same superview) or parent and child (superview and subview) — the only requirement is that they share a common ancestor (a superview at some height up the view hierarchy).

Here are the chief properties of an NSLayoutConstraint:

firstItem
firstAttribute
secondItem
secondAttribute

The two views and their respective attributes involved in this constraint. If the constraint is describing a view’s absolute height or width, the second view will be nil and the second attribute will be NSLayoutAttributeNotAnAttribute (which you’ll probably write as 0). Additional attribute types are:

  • NSLayoutAttributeLeft
  • NSLayoutAttributeRight
  • NSLayoutAttributeTop
  • NSLayoutAttributeBottom
  • NSLayoutAttributeLeading
  • NSLayoutAttributeTrailing
  • NSLayoutAttributeWidth
  • NSLayoutAttributeHeight
  • NSLayoutAttributeCenterX
  • NSLayoutAttributeCenterY
  • NSLayoutAttributeBaseline

The meanings of the attributes are intuitively obvious, except that you might be wondering about what “leading” and “trailing” mean: they are the international equivalent of “left” and “right”, automatically reversing their meaning on systems whose language is written right-to-left (making it easy, say, to align the beginnings of several labels of different lengths, irrespective of localization).

multiplier
constant
Numbers to be applied to the second attribute’s value to determine the first attribute’s value. The multiplier is multiplied by the second attribute’s value; the constant is added to that product. (The name constant is a very poor choice, as this value isn’t constant; have the Apple folks never heard the term addend?) Basically, you’re writing an equation of the form a1 = ma2 + c, where a1 and a2 are the two attributes, and m and c are the multiplier and the constant. Thus, in the most degenerate case, when the first attribute’s value is to equal the second attribute’s value, the multiplier will be 1 and the constant will be 0.
relation
How the two attribute values are to be related to one another, as modified by the multiplier and the constant. This is the operator that goes in the spot where I put the equal sign in the equation in the preceding paragraph. It might be an equal sign (NSLayoutRelationEqual, which you’ll probably write as 0), but then again it might not; inequalities are also permitted (NSLayoutRelationLessThanOrEqual, NSLayoutRelationGreaterThanOrEqual).
priority
Priority values range from 1000 (required) down to 1, and certain standard behaviors have standard priorities. Constraints can have different priorities, determining the order in which they are applied. Constraints are permitted to conflict with one another provided they have different priorities.

A constraint belongs to a view. A view can have many constraints: a UIView has a constraints property, along with instance methods addConstraint:, addConstraints:, removeConstraint:, and removeConstraints:.

The question then is which view a given constraint should belong to. The answer is absolute: it is the closest superview of both views involved in a constraint. Thus, for example, if the constraint dictates a view’s absolute width, it belongs to that view; if it aligns the tops of two sibling views, it belongs to their superview; if it sets the top of a view in relation to the top of its superview, it belongs to the superview. The runtime may permit you to cheat and add a constraint at too high a level, but adding a constraint that refers to a view outside the subview hierarchy of the view to which you add it will cause a crash (with a helpful error message).

Both views involved in a constraint must be present in the view hierarchy before the constraint can be added.

NSLayoutConstraint properties are read-only, except for priority and constant. In Chapter 17, it will turn out that changing a constraint’s constant in real time is a good way to animate a view. If you want to change anything else about a constraint, you must remove the constraint and add a new one.

If you use constraints, and if a view is created either in code or in a nib where “Use autolayout” is unchecked, that view’s autoresizingMask is automatically translated into constraint form and applied in addition to any constraints you apply. It isn’t one of the view’s constraints, but it’s operating as a set of constraints anyway. If that isn’t what you want — that is, if only the constraints you apply are to be treated as constraints — you must explicitly turn off this behavior by setting the view’s translatesAutoresizingMaskIntoConstraints property to NO, because the default is YES.

Note

It may seem inconvenient to have to keep turning off translatesAutoresizingMaskIntoConstraints; why is the default YES? For a very good reason: that’s what allows you to use autolayout for some views and autoresizing for others. If a minimum of just one view has a minimum of just one explicit constraint, the entire autolayout system springs to life throughout your entire interface. This doesn’t cause havoc, because all the views without explicit constraints have translatesAutoresizingMaskIntoConstraints turned on by default, so their autoresizing masks are implicitly translated into constraints (hence the name!) and the layout dictated by those autoresizing masks continues to work.

We are now ready to write some code involving constraints! I’ll generate the same views and subviews and layout behavior as in Figure 14.12 and Figure 14.13, but using constraints:

UIView* v1 = [[UIView alloc] initWithFrame:CGRectMake(100, 111, 132, 194)];
v1.backgroundColor = [UIColor colorWithRed:1 green:.4 blue:1 alpha:1];
UIView* v2 = [UIView new];
UIView* v3 = [UIView new];
v2.backgroundColor = [UIColor colorWithRed:.5 green:1 blue:0 alpha:1];
v3.backgroundColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:1];
[self.window.rootViewController.view addSubview: v1];
[v1 addSubview: v2];
[v1 addSubview: v3];

v2.translatesAutoresizingMaskIntoConstraints = NO;
v3.translatesAutoresizingMaskIntoConstraints = NO;
[v1 addConstraint:
 [NSLayoutConstraint
  constraintWithItem:v2 attribute:NSLayoutAttributeLeft
  relatedBy:0
  toItem:v1 attribute:NSLayoutAttributeLeft
  multiplier:1 constant:0]];
[v1 addConstraint:
 [NSLayoutConstraint
  constraintWithItem:v2 attribute:NSLayoutAttributeRight
  relatedBy:0
  toItem:v1 attribute:NSLayoutAttributeRight
  multiplier:1 constant:0]];
[v1 addConstraint:
 [NSLayoutConstraint
  constraintWithItem:v2 attribute:NSLayoutAttributeTop
  relatedBy:0
  toItem:v1 attribute:NSLayoutAttributeTop
  multiplier:1 constant:0]];
[v2 addConstraint:
 [NSLayoutConstraint
  constraintWithItem:v2 attribute:NSLayoutAttributeHeight
  relatedBy:0
  toItem:nil attribute:0
  multiplier:1 constant:10]];
[v3 addConstraint:
 [NSLayoutConstraint
  constraintWithItem:v3 attribute:NSLayoutAttributeWidth
  relatedBy:0
  toItem:nil attribute:0
  multiplier:1 constant:20]];
[v3 addConstraint:
 [NSLayoutConstraint
  constraintWithItem:v3 attribute:NSLayoutAttributeHeight
  relatedBy:0
  toItem:nil attribute:0
  multiplier:1 constant:20]];
[v1 addConstraint:
 [NSLayoutConstraint
  constraintWithItem:v3 attribute:NSLayoutAttributeRight
  relatedBy:0
  toItem:v1 attribute:NSLayoutAttributeRight
  multiplier:1 constant:0]];
[v1 addConstraint:
 [NSLayoutConstraint
  constraintWithItem:v3 attribute:NSLayoutAttributeBottom
  relatedBy:0
  toItem:v1 attribute:NSLayoutAttributeBottom
  multiplier:1 constant:0]];

Now, I know what you’re thinking. You’re thinking: “What are you, nuts? That is a boatload of code!” (Except that you probably used another four-letter word instead of “boat”.) But that’s something of an illusion. I’d argue that what we’re doing here is actually simpler than the code with which we created Figure 14.12 through autoresizing:

  • We create eight constraints in eight commands; I’ve broken each command into multiple lines, but that’s just a matter of formatting. They’re verbose, but they are the same command repeated with different parameters, so creating them is just a matter of copy-and-paste.
  • It looks as though just two commands (setting the autoresizing masks of the two subviews) have been replaced by eight commands. But we’re getting more bang for our buck than that: we’re no longer assigning v2 and v3 an initial frame! The constraints describe the positions of those views from the outset, and this, in constraint terms, remains their position even as their superview is resized. So constraints replace both setting a view’s autoresizing mask and setting its frame (or bounds and center) as a way of describing its layout.
  • Constraints are a far clearer expression of what’s supposed to happen than setting the autoresizing mask.
  • Constraints are also a far clearer expression of a subview’s position than setting its frame! For example, in describing v3’s position, we don’t have to use any math to place its frame origin. Recall what we had to say before:

    v3 = [[UIView alloc] initWithFrame:CGRectMake(v1.bounds.size.width-20,
                                                  v1.bounds.size.height-20,
                                                  20, 20)];

    That business of subtracting the view’s height and width from its superview’s bounds height and width in order to place the view is skanky and error-prone. With constraints, we can speak the truth directly; our code says, plainly and simply, “v3 is 20 points wide and 20 points high and it’s flush with the bottom right corner of v1”.

In addition, constraints can express things that autoresizing can’t. For example, instead of applying an absolute height to v2, we could require that its height be exactly one-tenth of v1’s height, regardless of how v1 is resized. That sort of thing is utterly impossible without constraints (unless you implement layoutSubviews and enforce it manually, in code).

If you really find our code too verbose, it may be possible to condense it somewhat. Instead of creating each constraint individually, we can sometimes describe multiple constraints simultaneously through a sort of text-based shorthand, called a visual format. The shorthand is best understood by example:

@"V:|[v2(10)]"

In that expression, V: means that the vertical dimension is under discussion; the alternative is H:, which is also the default (so it is permitted to specify no dimension). A view’s name appears in square brackets, and a pipe (|) signifies the superview, so here we’re describing v2’s top edge as butting up against its superview’s top edge. Numeric dimensions appear in parentheses, and a numeric dimension accompanying a view’s name sets that dimension of that view, so here we’re also taking advantage of this specification of the vertical dimension to set v2’s height to 10.

To use a visual format, you have to provide a dictionary mapping the string name of each view mentioned to the actual view. For example, the dictionary accompanying the preceding expression might be @{@"v2":v2}. We can form this dictionary automatically with a macro, NSDictionaryOfVariableBindings, which takes a list of variable names. So here’s another way of expressing of the preceding code example, using the visual format shorthand throughout:

UIView* v1 = [[UIView alloc] initWithFrame:CGRectMake(100, 111, 132, 194)];
v1.backgroundColor = [UIColor colorWithRed:1 green:.4 blue:1 alpha:1];
UIView* v2 = [UIView new];
UIView* v3 = [UIView new];
v2.backgroundColor = [UIColor colorWithRed:.5 green:1 blue:0 alpha:1];
v3.backgroundColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:1];
[self.window.rootViewController.view addSubview: v1];
[v1 addSubview: v2];
[v1 addSubview: v3];

NSDictionary *vs = NSDictionaryOfVariableBindings(v2,v3);
v2.translatesAutoresizingMaskIntoConstraints = NO;
v3.translatesAutoresizingMaskIntoConstraints = NO;
[v1 addConstraints:
 [NSLayoutConstraint
  constraintsWithVisualFormat:@"H:|[v2]|"
  options:0 metrics:nil views:vs]];
[v1 addConstraints:
 [NSLayoutConstraint
  constraintsWithVisualFormat:@"V:|[v2(10)]"
  options:0 metrics:nil views:vs]];
[v1 addConstraints:
 [NSLayoutConstraint
  constraintsWithVisualFormat:@"H:[v3(20)]|"
  options:0 metrics:nil views:vs]];
[v1 addConstraints:
 [NSLayoutConstraint
  constraintsWithVisualFormat:@"V:[v3(20)]|"
  options:0 metrics:nil views:vs]];

That example creates the same constraints as the previous example, but in four commands instead of eight.

The visual format syntax shows itself to best advantage when multiple views are laid out in relation to one another along the same dimension; in that situation, you get a lot of bang for your buck (many constraints generated by one visual format string). The syntax, however, is somewhat limited in what constraints it can express; it conceals the number and exact nature of the constraints that it produces; and personally I find it easier to make a mistake with the visual format syntax than with the complete expression of each constraint. Still, you’ll want to become familiar with it, not least because console messages describing a constraint sometimes use it.

Here are some further things to know when generating constraints with the visual format syntax:

  • The metrics: parameter is a dictionary of NSNumber values. This lets you use a name in the visual format string where a numeric value needs to go.
  • The options: parameter is a bitmask letting you do things like add alignments. The alignments you specify are applied to all the views mentioned in the visual format string.
  • To specify the distance between two successive views, use hyphens surrounding the numeric value, like this: @"[v1]-20-[v2]". The numeric value may optionally be surrounded by parentheses. A single hyphen means that a default distance should be used.
  • A numeric value in parentheses may be preceded by an equality or inequality operator, and may be followed by an at sign with a priority. Multiple numeric values, separated by comma, may appear in parentheses together. For example: @"[v1(>=20@400,<=30)]".

You can make two major kinds of mistake with constraints:

Conflict

You can apply constraints that can’t be satisfied simultaneously. This will be reported in the console (at great length). Only required constraints (priority 1000) can contribute to a conflict, as the runtime is free to ignore lower-priority constraints that it can’t satisfy. For example, to the previous code, append this line:

[v1 addConstraints:
 [NSLayoutConstraint
  constraintsWithVisualFormat:@"V:[v3(10)]|"
  options:0 metrics:nil views:vs]];

The height of v3 can’t be both 10 (as here) and 20 (as in the preceding line). The runtime reports the conflict, and tells you which constraints are causing it.

Underdetermination (ambiguity)

You haven’t supplied sufficient information to determine the size and position of some view. This is a far more insidious problem, because nothing bad may seem to happen, so you might not discover it until much later. If you’re lucky, the view will at least fail to appear, or will appear in an undesirable place, alerting you to the problem. For example, in the last line of the previous code, we set the height of v3 to 20; suppose we remove that specification:

[v1 addConstraints:
 [NSLayoutConstraint
  constraintsWithVisualFormat:@"V:[v3]|"
  options:0 metrics:nil views:vs]];

Fortunately, v3 fails to appear in the interface, so we know we’ve made a mistake.

To help you analyze ambiguity, log a view’s hasAmbiguousLayout property (a BOOL); be sure to remove that call before submitting your app to the App Store. Alternatively, pause the running app and ask the debugger for the key window’s _autolayoutTrace; ambiguously laid out views are clearly marked:

(lldb) po [[UIWindow keyWindow] _autolayoutTrace]
(id) $1 = 0x074a41a0
*<UIWindow:0x749b890>
|   *<UIView:0x749ccb0>
|   |   *<UIView:0x749c280>
|   |   |   *<UIView:0x749c790>
|   |   |   *<UIView:0x749c930> - AMBIGUOUS LAYOUT

I find it useful to set up a category on NSLayoutConstraint with a method that lets me check a view and all its subviews at any depth for ambiguity:

@implementation NSLayoutConstraint (Ambiguity)
+ (void) reportAmbiguity:(UIView*) v {
    if (nil == v)
        v = [[UIApplication sharedApplication] keyWindow];
    for (UIView* vv in v.subviews) {
        NSLog(@"%@ %i", vv, vv.hasAmbiguousLayout);
        if (vv.subviews.count)
            [self reportAmbiguity:vv];
    }
}
@end

Given the notions of conflict and ambiguity, we can understand what priorities are for. Imagine that all constraints have been placed in boxes, where each box is a priority value, in descending order. The first box (1000) contains all the required constraints, so all required constraints are obeyed first. (If they conflict, that’s bad, and a report appears in the log; meanwhile, the system implicitly lowers the priority of one of the conflicting constraints, so that it doesn’t have to obey it and can continue with layout by satisfying the remaining required constraints.) If there still isn’t enough information to perform unambiguous layout given the required priorities alone, we pull the constraints out of the next box and try to obey them. If we can, consistently with what we’ve already done, fine; if we can’t, or if ambiguity remains, we look in the next box — and so on. For a box after the first, we don’t care about obeying exactly the constraints it contains; if an ambiguity remains, we can use a lower-priority constraint value to give us something to aim at, resolving the ambiguity, without fully obeying the lower-priority constraint’s desires. For example, an inequality is an ambiguity, because an infinite number of values will satisfy it; a lower-priority equality can tell us what value to prefer, resolving the ambiguity, but there’s no conflict even if we can’t fully achieve that preferred value.

Some built-in interface objects have an inherent size in one or both dimensions, so they are not ambiguously laid out even if no explicit NSLayoutConstraint dictates their size. Rather, the inherent size is used to generate constraints implicitly. For example, a button has a standard height, and its width is determined by its title. This inherent size is the object’s intrinsic content size. You can supply an intrinsic size in your own custom UIView subclass by implementing intrinsicContentSize. If you need the runtime to call intrinsicContentSize again, because that size has changed and the view needs to be laid out afresh, send your view the invalidateIntrinsicContentSize message.

The tendency of an interface object to size itself to its intrinsic content size can conflict with its tendency to obey explicit constraints. For example, we wouldn’t want a UILabel to extend out of its superview, no matter how long its text may be; if the text isn’t permitted to wrap, it should be truncated. Therefore these tendencies have a priority:

contentHuggingPriorityForAxis:
The tendency of a view to refuse to grow larger than its intrinsic size in this dimension. The default is 250 (also known as UILayoutPriorityDefaultLow).
contentCompressionResistancePriorityForAxis:
The tendency of a view to refuse to shrink smaller than its intrinsic size in this dimension. The default is 750 (also known as UILayoutPriorityDefaultHigh).

(The dimensions are UILayoutConstraintAxisHorizontal and UILayoutConstraintAxisVertical.) Those methods are getters; there are corresponding setters. You’re unlikely, however, to change the priorities for these tendencies — at least, you’re unlikely to change them by much. A situation where you would possibly need to change them is when two views with an intrinsic content size are pinned to one another. This can result in an ambiguity. Of two adjacent labels, which should be truncated if the superview gets narrower? To dictate the answer, it suffices to raise the compression resistance priority of one of the labels by a single point.

Another common situation is where you’ll want to lower the priority of some other constraint, to allow intrinsic content size to predominate. An example that Apple gives is a label to the left of a centered button: the button’s bottom is pinned to the superview’s bottom, and the label and button are pinned to one another with their baselines aligned, and the button’s horizontal center is pinned to its superview’s horizontal center. If the label’s text grows longer (or the superview’s width grows narrower), the label should not stretch leftward past the left side of its superview, so it has an inequality constraint pinning its left at a guaranteed minimum distance from the superview’s left. But the text should not then be truncated if it doesn’t have to be, so the priority with which the button is horizontally centered is made lower than the label’s compression resistance priority:

self.button.translatesAutoresizingMaskIntoConstraints = NO;
self.label.translatesAutoresizingMaskIntoConstraints = NO;
id button = self.button;
id label = self.label;

NSDictionary* d = NSDictionaryOfVariableBindings(button,label);
[self.view addConstraints:
 [NSLayoutConstraint
  constraintsWithVisualFormat:@"V:[button]-|"
  options:0 metrics:nil views:d]];
[self.view addConstraints:
 [NSLayoutConstraint
  constraintsWithVisualFormat:@"H:|-(>=10)-[label]-[button]-(>=10)-|"
  options:NSLayoutFormatAlignAllBaseline metrics:nil views:d]];
NSLayoutConstraint* con =
 [NSLayoutConstraint
  constraintWithItem:button attribute:NSLayoutAttributeCenterX
  relatedBy:0
  toItem:self.view attribute:NSLayoutAttributeCenterX
  multiplier:1 constant:0];
con.priority = 700;
[self.view addConstraint:con];

Behind the scenes, compression resistance and content hugging work like this: Suppose the view’s intrinsic width is 100 points. Then two implicit constraints are generated, an inequality saying that the view’s width should be greater than or equal to 100 points, but with a priority of 750, and an inequality saying that the view’s width should be less than or equal to 100 points, but with a priority of 250. All other things being equal, the view’s width will be exactly 100 points, as this satisfies both inequalities unambiguously. But higher-priority constraints could cause the width to be less or greater. In the preceding example, as the label’s text becomes longer, its intrinsic width grows. The first inequality says we may make the label’s width greater than its intrinsic width, at a priority of 750, but the button’s tenacity to hold its position in the center is even less, at a priority of 700, so the label adopts its intrinsic width and the button shifts right to obey the spacing constraint between them (which is required). But there is also an inequality spacing constraint on the button’s right side (which is also required); eventually the button moves so far to the right that it can move no further without violating that constraint, which is impossible. At that point, since the first inequality cannot be satisfied, the second inequality is consulted; it permits the label to be narrower than its intrinsic content, the label stops growing to match its growing intrinsic width, and its text is truncated instead.

Constraints in the Nib

In a nib where “Use autolayout” is checked, when you add a view to a superview or move or resize it within its superview, the nib editor adds constraints. A constraint is an object, so these are nib objects. Constraints in the nib are visible in three places:

In the expanded dock
Constraints are listed in a special category, “Constraints”, under the view to which they belong. You’ll have a much easier time distinguishing these constraints if you give your views meaningful labels! (See Chapter 7.) Select a constraint and show the Attributes inspector to view its numeric value, its priority, and so on.
In the canvas
Constraints appear graphically as dimension lines. Select a constraint and show the Attributes inspector to view its numeric value, its priority, and so on.
In the Size inspector
When a view involved in any constraints is selected, the Size inspector displays those constraints, along with the view’s content hugging priority and compression resistance priority in each dimension. For example, in Figure 14.14, I’ve started to set up a view and a subview like v1 and v2 in the earlier code example; the subview is selected, so its constraints are shown in the Size inspector.
figs/pios_1414.png

Figure 14.14. A view’s constraints displayed in the nib


The nib editor’s automatic creation of constraints in the nib is clever, but it presents two major challenges:

  • It doesn’t know what you really intend to have happen if a view’s superview is resized at runtime.
  • It won’t let you create, even temporarily, an ambiguous or conflicted layout — but it will let you create a layout that could generate a conflict if a superview is resized at runtime.

As a result, you’ll often find yourself trying to correct a misapprehension on the nib editor’s part, and you’ll find that this can be quite tricky. Figure 14.14 shows a case in point. Of the automatically generated constraints affecting the subview, three are correct — the left, right, and top space between the subview and its superview — but the fourth is not. Xcode has created a constraint describing the space between the bottom of the subview and the bottom of the superview; I want that space free to change. Instead, I want to fix the subview’s height (at 10 points). But Xcode won’t let me delete the unwanted constraint, because that would cause ambiguous layout.

The solution is to create the new constraint first. To do so, select the affected view(s) and choose from the Editor → Align or Editor → Pin menu (or use the first or second pop-up menu in the floating cartouche at the lower right of the canvas). In this case, we want to constrain the subview’s height to its current height, so we select the subview and choose Editor → Pin → Height.

This generates an additional constraint. Unfortunately we now have a potential conflict. Our subview has a height constraint of 10 points; meanwhile, its top is still pinned to the top of its superview, and its bottom is still pinned to the bottom of its superview. Xcode permits this situation because it is legal now, but if the superview’s height changes at runtime, it won’t be possible to satisfy all of those constraints; something needs to give. In this situation, you can do one of three things:

Delete the unwanted constraint
In this case, that’s the simplest solution. We can select the constraint that pins the bottom of the subview to the bottom of its superview and delete it. In more complicated situations, though, the nib editor might not permit that.
Lower the priority of the unwanted constraint
If the unwanted constraint has a low enough priority, it will have no effect on the layout. You can lower the constraint’s priority by selecting the constraint and adjusting the Priority slider in the Attributes inspector.
Remove the unwanted constraint in code
If you’re really desperate, you can make an outlet to the unwanted constraint and use this to remove the constraint in code after the nib loads (but before layout actually takes place). I’ve found myself in situations so complex that this was my only recourse; I simply couldn’t bully the nib editor into letting me do what I wanted, even though that wouldn’t have resulted in an ambiguous layout.

Another maddening feature of the nib editor is its tendency to change what’s selected. For example, when you select the subview and choose Editor → Pin → Height, the newly created constraint is selected instead of the subview. If what you’re trying to do is create several constraints having to do with a certain view, Xcode keeps deselecting the view and you have to keep reselecting it.

The nib editor, like the visual format syntax, can’t specify every possible constraint. For example, you can’t express a multiplier, or a relationship between one view and another that isn’t its sibling or superview. Sometimes there’s no alternative to adding or adjusting a constraint in code after the nib loads.

Still, once you get used to it, you’ll find that being able to set up constraints in the nib is really nice. It’s quite easy to construct an interface that keeps working correctly when the app launches on the iPhone 5 (with its taller screen) vs. the iPhone 4, or when the app is rotated. Figure 14.15 shows two subviews with their superview margins set and configured to have their widths equal to one another. This was easily attained by selecting both views and choosing Editor → Pin → Widths Equally. The result, which looks great both on the iPhone 5 and on the iPhone 4, requires no code; without constraints, only manual layout (in layoutSubviews) could have achieved the same thing.

figs/pios_1415.png

Figure 14.15. An equal widths constraint configured in the nib


Here’s how to achieve in code the constraints created by the nib in Figure 14.15 (in the view controller’s viewDidLoad). Presume that we are starting with a nib where “Use autolayout” is unchecked, so it contains no constraints; and presume we have outlets to the two subviews, v1 and v2:

UIView* sup = self.view;
v1.translatesAutoresizingMaskIntoConstraints = NO;
v2.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary* d = NSDictionaryOfVariableBindings(sup, v1, v2);
[sup addConstraints:
 [NSLayoutConstraint
  constraintsWithVisualFormat:@"|-[v1]-(40)-[v2(==v1)]-|"
  options:0 metrics:0 views:d]];
[sup addConstraints:
 [NSLayoutConstraint
  constraintsWithVisualFormat:@"V:|-[v1]-|"
  options:0 metrics:0 views:d]];
[sup addConstraints:
 [NSLayoutConstraint
  constraintsWithVisualFormat:@"V:|-[v2]-|"
  options:0 metrics:0 views:d]];

I’ll modify that same code to illustrate the notion of removing and adding constraints in code after the nib loads. Presume this time that our nib’s “Use autolayout” is checked. But let’s pretend that we can’t set up the horizontal spacing and width constraints in the nib; instead, both subviews have all four edges pinned to the corresponding edge of the superview. We already have outlets v1 and v2 to the two subviews; in addition, we create outlets cons1 and cons2 to the two unwanted constraints — the left subview’s right constraint and the right subview’s left constraint. Then, in the view controller’s viewDidLoad, we simply remove those constraints and replace them with what we really want:

UIView* sup = self.view;
[sup removeConstraints:@[cons1, cons2]];
NSDictionary* d = NSDictionaryOfVariableBindings(sup, v1, v2);
[sup addConstraints:
 [NSLayoutConstraint
  constraintsWithVisualFormat:@"|-[v1]-(40)-[v2(==v1)]-|"
  options:0 metrics:0 views:d]];

Another situation in which the nib editor may misunderstand what you intend is when a view has an intrinsic size. Should a label or a button, for example, be assigned an explicit width constraint, or should its width be governed by its intrinsic size? Xcode tries to guess which you want, based on the signals you give it; so it helps to know what signals Xcode is watching for. If you set a view’s size explicitly, Xcode will probably assume that you want an explicit size constraint. If you select a view and choose Editor → Size to Fit Content, Xcode will remove the view’s explicit size constraints, letting the intrinsic size predominate.

Note

You can’t turn off autolayout for just part of a nib. Either all views have constraints or all views use autoresizing. There’s no nib equivalent to the translatesAutoresizingMaskIntoConstraints property for an individual view. A workaround, if you’re bent on generating different parts of your interface in nibs, one part with autoresizing, another part with autolayout, is to separate those parts into different nibs and then combine them at runtime.

Warning

If a nib’s “Use autolayout” is checked, do not load it on any system earlier than iOS 6. If you do, your app will crash, because you’re using a class, NSLayoutConstraint, that doesn’t exist before iOS 6. Unfortunately, “Use autolayout” is checked, by default, in all nibs created in Xcode 4.5 and later.

Order of Layout Events

When the moment comes to lay out a view, the following events take place:

  1. The view and its subviews are sent updateConstraints, starting at the bottom of the hierarchy (the deepest subview) and working up to the top (the view itself, possibly the root view). This event may be omitted for a view if its constraints have not changed.

    You can override updateConstraints in a subclass. You might do this, for example, if your subclass is capable of altering its own constraints and you need a signal that now is the time to do so. You must call super or the app will crash (with a helpful error message). You should never call updateConstraints; to trigger an immediate call to updateConstraints, send a view the updateConstraintsIfNeeded message.

    If you’re not using autolayout, updateConstraints by default won’t be sent to your view as part of the layout process. If you want it to be sent, send setNeedsUpdateConstraints to the view. You’ll then get updateConstraints events from then on, whenever layout is to be performed.

  2. The view and its subviews are sent layoutSubviews, starting at the top of the hierarchy (the view itself, possibly the root view) and working down to the bottom (the deepest subview).

    You can override layoutSubviews in a subclass in order to take a hand in the layout process. If you’re not using autolayout, layoutSubviews does nothing by default; layoutSubviews is your opportunity to perform manual layout after autoresizing has taken place. If you are using autolayout, you must call super or the app will crash (with a helpful error message). You should never call layoutSubviews; to trigger an immediate call to layoutSubviews, send a view the layoutIfNeeded message (which may cause layout of the entire view tree, not only below but also above this view), or send setNeedsLayout to trigger a call to layoutSubviews later on, after your code finishes running, when layout would normally take place.

When you’re using autolayout, what happens in layoutSubviews? The runtime examines all the constraints affecting this view’s subviews, works out values for their frames, and assigns those views those frames. In other words, layoutSubviews performs manual layout! The constraints are merely instructions attached to the views; layoutSubviews reads them and responds accordingly, setting frames in the good old-fashioned way.

Knowing this, you might override layoutSubviews when you’re using autolayout, in order to tweak the outcome. First you call super, causing all the subviews to adopt their new frames. Then you examine those frames. If you don’t like the outcome, you can change the situation, removing subviews, adding or removing constraints, and so on — and then you call super again, to get a new layout outcome.

Unless you explicitly demand immediate layout, layout isn’t performed until your code finishes running (and then only if needed). Moreover, ambiguous layout isn’t ambiguous until layout actually takes place. Thus, for example, it’s perfectly reasonable to cause an ambiguous layout temporarily, provided you resolve the ambiguity before layoutSubviews is called. (Your last best opportunity to do this is in updateConstraints!) That’s why it was legal for me, in the code at the end of the preceding section, to remove two constraints before adding some new ones; there was briefly an ambiguous layout situation, but no layout took place during that time.

On the other hand, a conflicting constraint conflicts the instant it is added. This code may cause a conflict report to appear in the log:

NSDictionary* d = NSDictionaryOfVariableBindings(sup, v1, v2);
[sup addConstraints:
 [NSLayoutConstraint
  constraintsWithVisualFormat:@"|-[v1]-(40)-[v2(==v1)]-|"
  options:0 metrics:0 views:d]];
[sup removeConstraints:@[cons1, cons2]];

If you try it and no conflict report appears, that’s only because this code is in the view controller’s viewDidLoad, which is called when the views in question are not yet part of the visible interface. If that same code were postponed to run at a later time, the addConstraints: call would generate the conflict report. The moral is clear: remove constraints that need to be removed before adding new ones that need to be added.

It is also possible to simulate layout of a view in accordance with its constraints and those of its subviews. This is useful for discovering ahead of time what the view’s size would be if layout were performed at this moment. Send the view the systemLayoutSizeFittingSize: message. The system will attempt to reach or at least approach the size you specify, at a very low priority; mostly likely you’ll specify either UILayoutFittingCompressedSize or UILayoutFittingExpandedSize, depending on whether what you’re after is the smallest or largest size the view can legally attain. I’ll show an example in Chapter 21.

Autolayout and View Transforms

Suppose I apply to a view a transform that shrinks it to half its size:

v.transform = CGAffineTransformMakeScale(0.5,0.5);

I expect the view to shrink in both dimensions, toward its center. Instead, under autolayout, I might see it shrink just its height, while its width holds steady, and instead of shrinking toward its center, it shrinks toward its bottom; and this could turn out to be because its width and bottom are determined by constraints.

The fact is that autolayout does not play well with view transforms. The reason, apparently, is that (as I explained in the preceding section) autolayout works by treating the constraints as a to-do list and obeying them in layoutSubviews by applying manual layout. If a constraint describes a view’s frame by pinning one of its edges, then autolayout will set that view’s frame. But setting a view’s frame is exactly what you’re not supposed to do when a view has a nonidentity transform.

Warning

A further problem is that applying a transform to a view triggers layout immediately. This feels like a bug.

One possible solution is this: if a view is to be given a transform, then take it out of autolayout’s influence altogether. In this code, I prepare to apply a transform to a view (self.otherView) by removing its constraints and any constraints of its superview (self.view) that affect it:

NSMutableArray* cons = [NSMutableArray array];
for (NSLayoutConstraint* con in self.view.constraints)
    if (con.firstItem == self.otherView || con.secondItem == self.otherView)
        [cons addObject:con];
[self.view removeConstraints:cons];
[self.otherView removeConstraints:self.otherView.constraints];

Unfortunately, this won’t quite do, as otherView vanishes from the screen. The reason is that we are still using autolayout, and now otherView has no constraints whatever, so it has neither size nor position! To complete our code, we must prevent autolayout from using constraints as a way of positioning otherView, which we can do by bringing its autoresizing mask back into play:

self.otherView.translatesAutoresizingMaskIntoConstraints = YES;

The result is that otherView is now treated exactly as it was before autolayout existed, and a transform applied to it works correctly.

Perhaps, however, that approach seems too drastic, as we have completely lost the benefit of being able to position our view through constraints. An alternative might be to use only constraints that don’t conflict with the transform we intend to apply. For example, if a view with an internally fixed width and height is positioned solely by pinning its center, then we can freely apply scale and rotation transforms to it, as these are applied around the view’s center and thus don’t conflict with the constraints that position it. Unfortunately, you probably won’t be able to arrange that in a nib, so you’ll have to use code to remove the existing constraints and then apply new ones:

// remove constraints affecting otherView's edges
NSMutableArray* cons = [NSMutableArray array];
for (NSLayoutConstraint* con in self.view.constraints)
    if (con.firstItem == self.otherView || con.secondItem == self.otherView)
        [cons addObject:con];
[self.view removeConstraints:cons];
// add constraints positioning otherView by its center
[self.view addConstraint:
 [NSLayoutConstraint
  constraintWithItem:self.otherView attribute:NSLayoutAttributeCenterX
  relatedBy:0
  toItem:self.view attribute:NSLayoutAttributeLeft
  multiplier:1 constant:self.otherView.center.x]];
[self.view addConstraint:
 [NSLayoutConstraint
  constraintWithItem:self.otherView attribute:NSLayoutAttributeCenterY
  relatedBy:0 toItem:self.view attribute:NSLayoutAttributeTop
  multiplier:1 constant:self.otherView.center.y]];

A further variant would be to use a host view and a subview. The host view is positioned by constraints, in the normal way: you can use the host view’s edges as part of the overall constraint-based layout. But the host view is also invisible. Now we can apply either of the preceding two solutions to the host view’s subview, which is visible. Either the subview has a fixed width and height, and is pinned by its center to the host view’s center, in which case scale and rotation transforms can be applied to it, or else we take the subview out of the influence of autolayout altogether, in which case any transform can be applied to it.

Still another solution is to use a layer transform (Chapter 16) instead of a view transform, since applying a layer transform doesn’t trigger layout.