coneybeare
coneybeare

Reputation: 33101

NSLayoutConstraint subviews won't autoresize to fit container view

I have a simple layout where there is a containerView that starts on the entire screen. Inside, I have two vertical subviews — topView (green), botView (orange) which need equal heights and should have 50% of the containerView height.

After launch, I slide in a view (blue) from offscreen up from the bottom, causing the containerView to resize to a shorter height.

What I would expect to happen is that the topView and botView would both get shorter, maintaining their even heights within the containerView, but what actually happens is that the inner views are not resized, despite having a constraint to do so.

enter image description here

Because this code has some code based constraints, and some IB ones, here is a sample project to see it in action. This is the constraint which is being ignored:

NSLayoutConstraint *c1 = [NSLayoutConstraint constraintWithItem:self.topView
                                                      attribute:NSLayoutAttributeHeight
                                                      relatedBy:NSLayoutRelationEqual
                                                         toItem:self.containerView
                                                      attribute:NSLayoutAttributeHeight
                                                     multiplier:0.5f
                                                       constant:0.f];
[self.containerView addConstraint:c1];

and the resize view code:

- (void)resizeViews {

    __weak UAViewController *weakSelf = self;
    dispatch_async(dispatch_get_main_queue(), ^{
        [UIView animateWithDuration:0.25 animations:^{
            weakSelf.slideInView.frame = CGRectMake(CGRectGetMinX(weakSelf.view.bounds),
                                                    CGRectGetMaxY(weakSelf.view.bounds) - CGRectGetHeight(weakSelf.slideInView.frame),
                                                    CGRectGetWidth(weakSelf.view.bounds),
                                                    CGRectGetHeight(weakSelf.slideInView.frame));

            weakSelf.containerView.frame = CGRectMake(CGRectGetMinX(weakSelf.view.bounds),
                                                      CGRectGetMinY(weakSelf.view.bounds),
                                                      CGRectGetWidth(weakSelf.view.bounds),
                                                      CGRectGetMaxY(weakSelf.view.bounds) - CGRectGetHeight(weakSelf.slideInView.frame));

        }];
    });

}

What am I doing wrong here?

Upvotes: 2

Views: 4311

Answers (1)

Rob
Rob

Reputation: 437442

I'm surprised you're setting the frame for a view. Generally in auto layout, you'd slide in the view by changing a constraint constant, not by changing a frame.

For example, imagine that your constraints for the container's subviews are defined like so (for simplicity's sake, since the two views are supposed to take up 50% of the container, most easily done by defining the two subviews to be the same height):

[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[topView][botView(==topView)]|" options:0 metrics:nil views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[topView]|" options:0 metrics:nil views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[botView]|" options:0 metrics:nil views:views]];

And, to keep things simple, define the relation between the container, the slide in view with relation to their superview like so:

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[containerView][slideInView]|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[containerView]|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[slideInView]|" options:0 metrics:nil views:views]];

// set the slide in view's initial height to zero

NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:slideInView
                                                                    attribute:NSLayoutAttributeHeight
                                                                    relatedBy:NSLayoutRelationEqual
                                                                       toItem:nil
                                                                    attribute:NSLayoutAttributeNotAnAttribute
                                                                   multiplier:1.0
                                                                     constant:0.0];
[self.view addConstraint:heightConstraint];

Then, when you want to slide in the "slide in view", just animate the changing of its height accordingly:

heightConstraint.constant = 100;
[UIView animateWithDuration:1.0 animations:^{
    [self.view layoutIfNeeded];
}];

Thus:

two views

Now, you can do this with heights that are x% of the height of the container, too, but you just can't do that easily with VFL, but it should work fine. Also you could slide in that "slide in view" by animating its top constraint, too (but that's a hassle because then you have to recalculate the top constraint on rotation, something I didn't want to do in this simple mockup).

But you get the idea. Just define the height or bottom constraint of the container view to be in relation to the slide in view, and then animate the slide in view by adjusting its constraints, and if you've defined all of your constraints properly, it will gracefully animate all four views (the container, the slide in view, and the container's two subviews) properly when you animate layoutIfNeeded after changing the slide in view's constraints.

Upvotes: 4

Related Questions