About Those Missing Views
It’s common for developers new to Auto Layout to “lose” views. They discover that views they have added end up offscreen or that they have a zero size due to constraints. (Incidentally, Auto Layout works with positive sizes, zero or larger. You cannot create views with negative widths or heights.) The missing views problem catches many devs. This problem happens with both underconstrained views and views with inconsistent rules.
In this section, you’ll see a little bit of constraint code, even before you’ve read about the details of the constraint class and how instances work. Please bear with me. I’ve added highlights to help explain ambiguous and underconstrained scenarios to make a point. If you work with Auto Layout, you should be aware of these situations before you start using the technology.
Underconstrained Missing Views
Underconstrained views don’t give Auto Layout enough information to build from, so it often defaults to a size of zero. Consider the following example. This code creates a new view, prepares it for Auto Layout, and then adds two sets of constraints, which I’ve highlighted in boldface:
// Create a new view and add it into the Auto Layout system // This view goes missing despite the initWithFrame: size UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 30.0f, 30.0f)]; [self.view addSubview:view]; view.translatesAutoresizingMaskIntoConstraints = NO; // Add two sets of rules, pinning the view and setting height [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view(==80)]" // 80 height options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)]];
The first set of constraints pins the view to the top of its superview and sets the height to 80. The second set pins the view to the superview’s leading edge. (This is the left side in the United States, with English’s left-to-right writing system.) I deliberately did not specify a width. The view’s size is, therefore, underconstrained.
You might expect Auto Layout to default to the initial frame size, which was set to 30 by 30 points. It does not. When this snippet sets translatesAutoresizingMaskIntoConstraints to NO, that initialization is essentially thrown away. As the view appears onscreen, the ambiguous rules passed to Auto Layout result in a width that falls to zero, creating a view that’s not visible:
2013-01-14 10:47:40.460 HelloWorld[73891:c07] <UIView: 0x884dfc0; frame = (0 0; 0 80); layer = <CALayer: 0x884e020>>
Missing Views with Inconsistent Rules
Inconsistent rules may also produce views that are missing in action. For example, imagine a pair of rules that say “View A is three times the width of View B” and “View B is twice the width of View A.” The following code snippets implement these rules. I’ve boldfaced the parts of the code that tell the rule story:
NSLayoutConstraint *constraint; constraint = [NSLayoutConstraint constraintWithItem:viewA attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:viewB attribute:NSLayoutAttributeWidth multiplier:3.0f constant:0.0f]; [self.view addConstraint:constraint]; constraint = [NSLayoutConstraint constraintWithItem:viewA attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:viewB attribute:NSLayoutAttributeWidth multiplier:2.0f constant:0.0f]; [self.view addConstraint:constraint];
Surprisingly, these two rules are neither unsatisfiable nor ambiguous, even though common sense suggests otherwise. That’s because both rules are satisfied when View A and View B have zero width. At zero, View A’s width can be three times the width of View B, and View B twice the width of View A:
0 = 0 * 3 and 0 = 0 * 2
When this code is run and the rules are applied, the views present the zero-width frames expected from this scenario:
2013-01-14 11:02:38.005 HelloWorld[74460:c07] <TestView: 0x8b30910; frame = (320 454; 0 50); layer = <CALayer: 0x8b309d0>> 2013-01-14 11:02:38.006 HelloWorld[74460:c07] <TestView: 0x8b32570; frame = (320 436; 0 68); layer = <CALayer: 0x8b32450>>
Tracking Missing Views
You can track down “missing” views with the debugger by inspecting their geometry after you expect them to appear (for example, in viewDidAppear: and awakeFromNib). You may want to add NSAssert statements about their expected size and positions. Some will be, as discussed, zero sized.
The following view, for example, had a zero-sized frame because it was underconstrained in the Auto Layout system:
2013-01-09 14:31:41.869 HelloWorld[29921:c07] View: <UIView: 0x71bb390; frame = (30 430; 0 0); layer = <CALayer: 0x71bb3f0>>
Other views may simply be offscreen because you haven’t told Auto Layout that the views must appear onscreen. For example, this view had a positive size (20 points by 20 points), but its frame with its (–20, –20) origin lay outside its view controller’s presentation:
2013-01-09 14:33:37.546 HelloWorld[29975:c07] View: <UIView: 0x7125f70; frame = (-20 -20; 20 20); layer = <CALayer: 0x7125fd0>>
In other cases, you might load a view from a storyboard or nib file and see only part of it onscreen, or it may occupy the entire screen at once. These are hallmarks of an underlying Auto Layout issue.