28

The new NSLayoutConstraint methods activateConstraints: and deactivateConstraints: don't appear to work correctly with IB-created constraints (they do work correctly for code-created constraints). I created a simple test app with one button that has two sets of constraints. One set, which is installed, has centerX and centerY constraints, and the other set, which is uninstalled, has top and left constraints (constant 10). The button method switches these constraints sets. Here is the code,

@interface ViewController ()
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *uninstalledConstraints;
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *installedConstraints;
@end

@implementation ViewController


- (IBAction)switchconstraints:(UIButton *)sender {
    [NSLayoutConstraint deactivateConstraints:self.installedConstraints];
    [NSLayoutConstraint activateConstraints:self.uninstalledConstraints];
}


-(void)viewWillLayoutSubviews {
    NSLog(@"installed: %@    uninstalled: %@", ((NSLayoutConstraint *)self.installedConstraints[0]).active ? @"Active" : @"Inactive", ((NSLayoutConstraint *)self.uninstalledConstraints[0]).active ? @"Active" : @"Inactive");

}

When the app launches, the button is in the correct, centered position defined by its installed constraints. After I do the activation/inactivation in the button's action method, the button moves to its new position correctly, but when I rotate the view to landscape, it moves back to its initially defined position (though a log still shows the newly activated set as being active). When I rotate back to portrait, the button stays in its initial position (centered in the screen), and now the log shows that initial set of constraints as active, and the ones I activated, as inactive.

The question is, is this a bug, or are these methods not supposed to work in this way with IB defined constraints?

5
  • Oooooh, good, now we're getting down to brass tacks. Can you post the project on github? I've been looking forward to this. Commented Dec 27, 2014 at 0:26
  • @matt, here's the link, jmp.sh/GWIN1Bg Commented Dec 27, 2014 at 2:18
  • Right, but isn't this because what you're doing with having uninstalled constraints in the storyboard with an outlet to them is incoherent? Commented Dec 27, 2014 at 2:46
  • I wish you had put it in github as I requested, because then I could push my fix at you. Instead, I have to describe it in words. Commented Dec 27, 2014 at 2:59
  • 2
    I had the same misunderstanding of "uninstalled" and largely the same problem, but it took me a long time searching to find this question and its solution. I've therefore edited the title to try to help the page's search-ability. Commented Nov 11, 2015 at 19:51

3 Answers 3

33

The problem is that you are doing something incoherent with "uninstalled" constraints in the storyboard. They are there but not there. "Uninstalled" constraints are for use only with size classes! You use them if you are going to let Xcode swap constraints for you automatically on rotation. Xcode can't cope with what you're doing. But if you create the second set of constraints in code, everything will work fine.

So, do this. Delete the two "uninstalled" constraints, and delete the uninstalledConstraints outlet. Now replace your entire view controller code with this:

@property (strong, nonatomic) NSMutableArray *c1;
@property (strong, nonatomic) NSMutableArray *c2;
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *installedConstraints;
@property (weak,nonatomic) IBOutlet UIButton *button;
@end

@implementation ViewController {
    BOOL did;
}

- (void)viewDidLayoutSubviews {
    NSLog(@"did");
    if (!did) {
        did = YES;
        self.c1 = [self.installedConstraints mutableCopy];
        self.c2 = [NSMutableArray new];
        [self.c2 addObject:
         [NSLayoutConstraint constraintWithItem:self.button attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTopMargin multiplier:1 constant:30]];
        [self.c2 addObject:
         [NSLayoutConstraint constraintWithItem:self.button attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeadingMargin multiplier:1 constant:30]];
    }
}

- (IBAction)switchconstraints:(UIButton *)sender {
    [NSLayoutConstraint deactivateConstraints:self.c1];
    [NSLayoutConstraint activateConstraints:self.c2];
    NSMutableArray* temp = self.c1;
    self.c1 = self.c2;
    self.c2 = temp;
}

Now repeatedly press the button. As you see, it jumps between the two positions. Now rotate the app; the button stays where it is.

Sign up to request clarification or add additional context in comments.

7 Comments

Sorry, I missed the Github part. You really didn't need to post any code, the one sentence, "Uninstalled" constraints are for us only with size classes!" would have been enough. It would be nice to know where that is documented. I've spent hours watching WWDC videos trying to find anything about activateConstraints/deactivateConstraints. The documentation on them is pretty thin.
The thing is that it's all totally orthogonal. The activate / deactivate has nothing whatever to do with the uninstalled constraints issue. This would have been just as much of a mess if you had used removeConstraints and addConstraints. It was the uninstalled constraints with the outlet to them that was killing everything. Use of installed/uninstalled constraints with multiple size classes is very well documented; that is what they are for. I know it's disappointing that you can't just draw both sets of constraints in the storyboard, but there it is.
I've seen the documentation for installing and uninstalling constraints with size classes, but what I seemed to have missed is any fundamental explanation of what it means for a constraint to be "installed" or "uninstalled". So, my (mis)understanding was that an uninstalled constraint was the IB equivalent of creating a constraint in code, but not adding it to the view. Can you point me to where this concept is well documented?
There's a WWDC video on writing adaptive apps that discusses what the "installed" checkbox is for. - I repeat, your desire to design both sets of constraints in Interface Builder is not irrational. On the contrary, you have a simple and persuasive use case. You should submit it as an enhancement request to Apple. But for now, you've stumbled into an edge case and should avoid it (as your own results demonstrate).
@matt you have a space between the self. and button where you create those constraints
|
16

I faced a similar situation.

I created two sets of NSLayoutConstraints in interface builder. Each set for one case. One set was "installed" the other not.

When I switch the case the coresponding set of layout constraint was activated and the other was deacticated. An as described in the Qusteion rotating forth and back does not work properly.

Is solved this by installing both sets in interface builder. And to get rid of the warnings I used a slighlt lower priority (999) for the second set. This worked for me.

Btw: Strang i used the "installed/not installed" aproach on another viewcontroller, their it worked.

In the not working case the viewcontroller was embedded in a containerview, perhaps that was the reason.

3 Comments

Aug 3 '16, I've upvoted this answer. Jun 21 '17, I can't upvote it twice :(
Additionally if you remove the containerview from its superview, it returns after screen rotation, really mysterious :P hopefully Apple will fix these flaws soon
I almost spent hours moving all constraints to code, but this answer prevented that. Thank you.
3

You can use this workflow, even though it’s clear that this is not a supported use case currently for the folks at Apple.

The trick is to not uninstall constraints (as @matt also pointed out), but to deactivate them in the viewDidLoad() of your UIViewController subclass, before layout occurs (and your conflicting constraints cause a problem).

I’m using that method now and it works perfectly. Ideally, of course, we’d have the ability to visually create groups of constraints and use them like keyframe simply by activating and deactivating them (with free animations in between) :)

1 Comment

What if those 2 constraints in the IB cause an error, you can't really continue with adding and lay-outing more views with constraints.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.