Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my superview-with-subview shrink when presented modally (using Auto Layout)?

Tags:

ios

autolayout

I have a view controller that owns a single custom view; the custom view draws a game board using Core Graphics. (No other subviews are involved.)

I set up Auto Layout constraints so that the game board fills its superview. When I set the view controller as the root view controller, then the game board (and superview) fills the screen, as is my intention. However, when I present the view controller modally, the game board (and superview) shrinks to nothing/minimum, and the Auto Layout trace reports the layout is ambiguous.

I wrote a simplified test case to illustrate the question.

Here is my BoardViewController, which has a top-level view with a green background, but creates a single expansive subview with a red background. I'd like to see the screen all red when this view controller takes over:

- (void)loadView
{
  UIView *mainView = [[UIView alloc] init];
  mainView.translatesAutoresizingMaskIntoConstraints = NO;
  mainView.backgroundColor = [UIColor greenColor];

  UIView *subView = [[UIView alloc] init];
  subView.translatesAutoresizingMaskIntoConstraints = NO;
  subView.backgroundColor = [UIColor redColor];

  [mainView addSubview:subView];
  self.view = mainView;

  NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(subView);
  NSArray *c1 = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[subView(>=10)]|"
                                                        options:0
                                                        metrics:nil
                                                          views:viewsDictionary];
  NSArray *c2 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[subView(>=10)]|"
                                                        options:0
                                                        metrics:nil
                                                          views:viewsDictionary];
  [self.view addConstraints:c1];
  [self.view addConstraints:c2];
}

If I set this as my root view controller, then I see my nice red game board filling the screen. However, the game board shrinks to the minimum 10x10 square when I present the BoardViewController from a different RootViewController:

- (void)loadView
{
  UIView *rootView = [[UIView alloc] init];
  rootView.translatesAutoresizingMaskIntoConstraints = NO;

  UIButton *presentButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  presentButton.translatesAutoresizingMaskIntoConstraints = NO;
  [presentButton setTitle:@"Present" forState:UIControlStateNormal];
  [presentButton addTarget:self
                    action:@selector(present:)
          forControlEvents:UIControlEventTouchUpInside];

  [rootView addSubview:presentButton];
  self.view = rootView;

  [rootView addConstraint:[NSLayoutConstraint constraintWithItem:presentButton
                                                       attribute:NSLayoutAttributeCenterX
                                                       relatedBy:NSLayoutRelationEqual
                                                          toItem:rootView
                                                       attribute:NSLayoutAttributeCenterX
                                                      multiplier:1.0
                                                        constant:0.0]];
  [rootView addConstraint:[NSLayoutConstraint constraintWithItem:presentButton
                                                       attribute:NSLayoutAttributeCenterY
                                                       relatedBy:NSLayoutRelationEqual
                                                          toItem:rootView
                                                          attribute:NSLayoutAttributeCenterY
                                                      multiplier:1.0
                                                      constant:0.0]];
}

- (void)present:(id)sender
{
  BoardViewController *bvc = [[BoardViewController alloc] init];
  [self presentViewController:bvc animated:YES completion:NULL];
}

I keep trying different Auto Layout rules, but no matter what I do, I can't get the game board to fill the screen when the view controller is presented modally. (At the same time I'm asking this question, I'm trying to get the runtime to tell me what it thinks is ambiguous. But I'm not very good this debugging yet, hence my question here.)

like image 586
Karl Voskuil Avatar asked May 28 '13 13:05

Karl Voskuil


2 Answers

After some investigation, it looks like when you push a modal view controller full screen, and the animation is complete, the presenting view controller along with its view is being removed from the view hierarchy. As expected, the views constraints are being removed then. Once you dismiss the modal view controller, the original viewcontroller with its view comes back again, but the constraints are gone and have not been saved and restored by the system. That is a bug. Even on iOS7. The reason why using NSAutoresizingConstraints works is, that the view system is able to rebuild the constraints by itself.

As a side note: if you have a setup with nested viewControllers (e.g. an outer viewcontroller embeds a navigation controller), then you can link these using the child viewcontrollers pattern. If you have done so, the topmost viewController view is being removed by what I described above. This means that you must only apply the NSAutoresizingConstraints patch to that topmost viewcontroller (typically the windows root viewcontroller) and use standard constraints wherever inbetween.

like image 56
berbie Avatar answered Sep 29 '22 11:09

berbie


Remove:

mainView.translatesAutoresizingMaskIntoConstraints = NO;

It's interesting that this works. It implies that when the view is presented modally using presentViewController it is expected to have constraints relating it to its superview, but when set as rootViewController of a UIWindow it doesn't need them...

like image 39
Mike Pollard Avatar answered Sep 29 '22 11:09

Mike Pollard