29

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?

jscs
  • 63,694
  • 13
  • 151
  • 195
rdelmar
  • 103,982
  • 12
  • 207
  • 218
  • Oooooh, good, now we're getting down to brass tacks. Can you post the project on github? I've been looking forward to this. – matt Dec 27 '14 at 00:26
  • @matt, here's the link, http://jmp.sh/GWIN1Bg – rdelmar Dec 27 '14 at 02: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? – matt Dec 27 '14 at 02: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. – matt Dec 27 '14 at 02: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. – jscs Nov 11 '15 at 19:51

3 Answers3

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.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 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. – rdelmar Dec 27 '14 at 04:24
  • 1
    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. – matt Dec 27 '14 at 04:26
  • 4
    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? – rdelmar Dec 27 '14 at 05:18
  • 3
    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 Dec 27 '14 at 15:13
  • @matt you have a space between the self. and button where you create those constraints – elliotrock Apr 15 '15 at 02:01
  • Is [Session 216](http://asciiwwdc.com/2014/sessions/216) the one you're talking about, @matt? – jscs Nov 12 '15 at 06:19
  • @JoshCaswell I think so. – matt Nov 12 '15 at 06:36
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.

Christian1313
  • 270
  • 3
  • 3
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) :)

Aral Balkan
  • 5,981
  • 3
  • 21
  • 24
  • 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. – kalafun Jan 17 '19 at 10:20