Resize current view using NSLayoutConstraints

10,643

Solution 1

As it turns out, NSPopoverController is an exception to the rule, and translatesAutoresizingMaskIntoConstraints must be YES for autolayout to work correctly.

Solution 2

There are a couple of issues:

  1. If you want your constraints to define the size of their containing view, they can't be ambiguous, as they are now. They have to fully define the space they need. For example:

    NSDictionary *views = @{@"v1" : self.v1,
                            @"v2" : self.v2};
    
    // Set horizontal constraints
    
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-[v1(200)]-|"  options:0 metrics:nil views:views]];
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-[v2(==v1)]-|" options:0 metrics:nil views:views]];
    
    // Set vertical constraints
    
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[v1]-[v2(600)]-|" options:0 metrics:nil views:views]];
    
    // Initialise height constraint for v1
    
    self.v1Height = [NSLayoutConstraint constraintWithItem:self.v1
                                                 attribute:NSLayoutAttributeHeight
                                                 relatedBy:NSLayoutRelationEqual
                                                    toItem:nil
                                                 attribute:NSLayoutAttributeNotAnAttribute
                                                multiplier:1
                                                  constant:50];
    [self addConstraint:self.v1Height];
    

    Note, I'm setting the width of v1, I'm setting v2 to be the same width as v1, and I'm setting the height of v2, also. That, combined with your subsequent creation of the v1Height constraint, now makes the view's layout unambiguous.

  2. Having done that, you also need to tell your view controller to inform the popover controller of its content size when the autolayout has been applied. Thus, your view controller could implement a viewDidLayoutSubviews:

    - (void)viewDidLayoutSubviews
    {
        [super viewDidLayoutSubviews];
    
        self.view.frame = CGRectMake(0.0, 0.0, self.view.frame.size.width, self.view.frame.size.height);
        self.contentSizeForViewInPopover = self.view.frame.size;
    }
    

    Note, that setting of the frame is very curious, but if you don't reset the origin to {0.0, 0.0}, your controls won't appear in the right place. It doesn't seem right that you have to do that, but in my experience, you do.

  3. Somewhat unrelated to your problem, you mention that you're removing v1Height "by removing it, recreating it, and then readding it". That's unnecessary and not recommended. The constant property is modifiable. As the docs say

    "Unlike the other properties, the constant may be modified after constraint creation. Setting the constant on an existing constraint performs much better than removing the constraint and adding a new one that's just like the old but for having a new constant."

    Thus, that means that you can do something as simple as:

    self.v1Height.constant = newHeight;
    [UIView animateWithDuration:0.5
                     animations:^{
                         [self layoutIfNeeded];
                     }];
    

Solution 3

Better later than never...

For Storyboard, IB and Swift:

Just control-drag your constraint like any other control to your viewController

@IBOutlet weak var spaceToTopThatIDecided: NSLayoutConstraint!

then change it simply:

spaceToTopThatIDecided.constant = 44

That's all!

Share:
10,643
sapi
Author by

sapi

Updated on June 05, 2022

Comments

  • sapi
    sapi almost 2 years

    I'm trying to set the layout of a view using NSLayoutConstraints.

    I can set things up within the view correctly, but I also need to be able to resize the current view (the one that I'm setting the constraints for) if the constraints require it. In other words, I want to set a fixed content size with a flexible window, rather than the reverse.

    So basically, I have:

    NSDictionary *views = [NSDictionary dictionaryWithObjectsAndKeys:self.v1, @"v1",
                           self.v2, @"v2",
                           nil];
    // Set horizontal constraints
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-[v1]-|"
                                                                 options:0
                                                                 metrics:nil
                                                                   views:views]];
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-[v2]-|"
                                                                 options:0
                                                                 metrics:nil
                                                                   views:views]];
    // Set vertical constraints
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[v1]-[v2]-|"
                                                                 options:0
                                                                 metrics:nil
                                                                   views:views]];
    // Initialise height constraint for v1
    self.v1Height = [NSLayoutConstraint constraintWithItem:self.v1
                                                    attribute:NSLayoutAttributeHeight
                                                    relatedBy:NSLayoutRelationEqual
                                                       toItem:nil
                                                    attribute:NSLayoutAttributeNotAnAttribute
                                                   multiplier:1
                                                     constant:50];
    

    If I later change self.v1Height (by removing it, recreating it, and then readding it), I would like the frame of self to expand (or contract) to accommodate the changed height.

    I'm not sure if it's relevant (as I'm more familiar with iOS than OS X), but this is the content view of a NSPopover.

    What constraint do I need to add to achieve this?