It’s common for developers new to Auto Layout to “lose” their views. They discover that views they have added end up offscreen or at that they have a zero size due to constraints. (Incidentally, Auto Layout works with positive sizes, zero or bigger. You cannot create views with negative widths and heights.) The missing views problem catches many devs. This problem happens both with underconstrained views and views with inconsistent rules.
In this section, you 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 that 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
With underconstrained views, there’s not enough information for the Auto Layout system 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 bolded:
// 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 parent and sets the height to 80. The second set pins the view to the parent’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 set translatesAutoresizingMaskIntoConstraints to NO, that initialization was 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 nonvisible view:
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 highlighted 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 a zero size, 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 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, viewWillAppear: 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>>
Others may simply be offscreen because you haven’t told Auto Layout that the views must appear onscreen. This view had a positive size (20 points by 20 points), but its frame with its (–20, –20) origin lay outside of 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 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.