6

I am adding a view as a subview using [self.view addSubview:myView]. This works fine in portrait mode. However, it doesn't work at all in landscape. How do I add layout constraints programatically?

My view currently looks like portrait rectangle and I need it to look like landscape rectangle in landscape mode.

I tried this code to see how constraints in code work but it always results in an exception. The code is:

[self.view addSubview:_preView];
NSLayoutConstraint *myConstraint = [NSLayoutConstraint
 constraintWithItem:_preView
 attribute:NSLayoutAttributeBottom
 relatedBy:NSLayoutRelationEqual
 toItem:self.view.superview
 attribute:NSLayoutAttributeBottom
 multiplier:1.0
 constant:-239];
[_preView addConstraint:myConstraint];

This always results in an exception. I know the above code just attempts to ensure that the bottom of preview is 239px above the bottom of main view. But that doesn't work either.

Could you help me out with sorting this so that I can resolve the landscape issue?

UPDATE

The exception generated is:

2013-08-05 16:13:28.889 Sample Code[33553:c07] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to install constraint on view. Does the constraint reference something from outside the subtree of the view? That's illegal. constraint:<NSLayoutConstraint:0x912c430 UIView:0x8561340.bottom == UILayoutContainerView:0x8257340.bottom - 20> view:<UIView: 0x85774e0; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; autoresizesSubviews = NO; layer = <CALayer: 0x8577490>>' *** First throw call stack: (0x1a04012 0x173be7e 0x1a03deb 0x12ee4a0 0xbb983e 0xbb9a27 0xbb9b76 0xbb9d3b 0xbb9c4d 0x1c0d9 0x11395b3 0x19c3376 0x19c2e06 0x19aaa82 0x19a9f44 0x19a9e1b 0x24027e3 0x2402668 0x67fffc 0x2d3d 0x2c65) libc++abi.dylib: terminate called throwing an exception (lldb)

I have added the subview before adding in the constraint so I am pretty sure the view is in hierarchy.

UPDATE 2

I set the parent view's property to `Autoresize Subviews' in IB. The subview now converts into landscape rectangle when the device is turned but its too narrow. I now need the code to make sure its of correct width maybe?

Gaurav Wadhwani
  • 1,352
  • 2
  • 15
  • 32
  • 1
    What exception does it give you? – jrturton Aug 05 '13 at 14:33
  • 2
    Did you set `translatesAutosizingMaskIntoConstraints` to `NO` for your programmatically created view? – Rob Aug 05 '13 at 14:35
  • possible duplicate of [NSGenericException', reason: 'Unable to install constraint on view](http://stackoverflow.com/questions/14833070/nsgenericexception-reason-unable-to-install-constraint-on-view) – Max MacLeod Aug 05 '13 at 15:34

3 Answers3

37

A couple of observations:

  1. Your constraint references a toItem of self.view.superview. I assume you meant self.view.

  2. You're adding the constraint to _preView, but you should add it to self.view (if you make the above change; if not, you'd use self.view.superview). You always add the constraint to the nearest shared parent.

  3. For the views you're creating programmatically, make sure to set translatesAutoresizingMaskIntoConstraints to NO.

    Thus:

    _preView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:_preView];
    NSLayoutConstraint *myConstraint = [NSLayoutConstraint constraintWithItem:_preView
                                                                    attribute:NSLayoutAttributeBottom
                                                                    relatedBy:NSLayoutRelationEqual
                                                                       toItem:self.view
                                                                    attribute:NSLayoutAttributeBottom
                                                                   multiplier:1.0
                                                                     constant:-239];
    [self.view addConstraint:myConstraint];
    

Chatting to you offline, two final observations:

  1. Your constraints were ambiguous. In the future, you can identify that by running the app in your debugger, hitting the pause button while the app is running (enter image description here) and then at the (lldb) prompt, you can enter

    po [[UIWindow keyWindow] _autolayoutTrace]

    ambiguous layout

    If you see AMBIGUOUS LAYOUT, then your constraints are not fully qualified (and thus you'll get unpredictable behavior). If you add the missing constraints, you should be able to eliminate this warning.

  2. If you want to animate constraint based views, you animate the changing of constant properties of the constraints, not by changing frame properties yourself. For example:

        // create subview
    
        UIView *subview = [[UIView alloc] init];
        subview.backgroundColor = [UIColor lightGrayColor];
        subview.translatesAutoresizingMaskIntoConstraints = NO;
        [self.view addSubview:subview];
    
        // create dictionary for VFL commands
    
        NSDictionary *views = @{@"subview" : subview, @"superview" : self.view};
    
        // add horizontal constraints
    
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[subview]|" options:0 metrics:nil views:views]];
    
        // set the height of the offscreen subview to be the same as its superview
    
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[subview(==superview)]" options:0 metrics:nil views:views]];
    
        // set the location of the subview to be just off screen below the current view
    
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:self.view.bounds.size.height];
        [self.view addConstraint:constraint];
    
        // then in two seconds, animate this subview back on-screen (i.e. change the top constraint `constant` to zero)
    
        double delayInSeconds = 2.0;
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
            constraint.constant = 0.0;
            [UIView animateWithDuration:1.0
                             animations:^{
                                 [self.view layoutIfNeeded];
                             }];
        });
    
Duck
  • 34,902
  • 47
  • 248
  • 470
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • While the app does not crash, it gives this in console: https://dl.dropboxusercontent.com/u/90817764/op.jpg And The constraint applied doesn't even work. – Gaurav Wadhwani Aug 05 '13 at 16:19
  • 3
    @GauravWadhwani As I mentioned in my comment above (and is mentioned in this message you shared with us), you should set `translatesAutosizingMaskIntoConstraints` to `NO` for your programmatically created views for which you'll be specifying constraints yourself. By default, iOS will assume that it has to create constraints for you, and these would appear to conflict with the ones you're trying to add. So, set set `translatesAutosizingMaskIntoConstraints` to `NO`, and you won't have any conflicts with any automatically generated constraints (because there won't be any). – Rob Aug 05 '13 at 16:28
  • So I should set to [self.view setTranslatesAutoresizingMaskIntoConstraints:NO]; ? – Gaurav Wadhwani Aug 05 '13 at 16:29
  • 1
    @GauravWadhwani You do this for the views you've programmatically created, not the views to which you're adding them. – Rob Aug 05 '13 at 16:31
  • Hey @Rob , thanks for your reply. I set [_preView setTranslatesAutoresizingMaskIntoConstraints:NO]; However, this causes _preView to be invisible on screen. I logged its origin on console and its showing visible co-ordinates. But nothing's on screen :( When I comment that line, the view is visible on screen again. – Gaurav Wadhwani Aug 05 '13 at 16:39
  • @GauravWadhwani let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/34856/discussion-between-rob-and-gaurav-wadhwani) – Rob Aug 05 '13 at 16:46
0

From your code above, there are 2 issues. 1. The constraint should be added to the parentview (self.view or self.view.superview as appropriate). 2. The items which are part of the myConstraint should be present in the view hierarchy to which you add your constraints.

My suggestion would be to check if your myConstraint can be formed with _preView and self.view , add the _preView to self.view as a subview and then add the myConstraint to self.view.

Also, the constraints should ideally be placed in -(void)updateConstraints method in your view (if you have a custom view) and you should call [self setNeedsUpdateConstraints]; in your view whenever you want the updateConstraints to be called on your view (after initializing your view, after rotation etc). You won't be calling updateConstraints directly.

Hetal Vora
  • 3,341
  • 2
  • 28
  • 53
  • So I added this method -(void)updateConstraints { NSLayoutConstraint *myConstraint = [NSLayoutConstraint constraintWithItem:_preView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view.superview attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-20]; [self.view addConstraint:myConstraint]; } And I call the method after my animations have loaded: [self.view setNeedsUpdateConstraints]; But it still results in the exception – Gaurav Wadhwani Aug 05 '13 at 15:03
  • As I mentioned, the views you refer to in your myConstraint, should be part of the view hierarchy to which you add the constraint. Hence, if your myConstraint is for preView and self.view.superview, your constraint will have to be added to self.view.superview. Also preView should already have been added to self.view or self.view.superview before the constraints are called. – Hetal Vora Aug 05 '13 at 15:10
  • What exception are you getting? Please provide it as well. – Hetal Vora Aug 05 '13 at 15:11
  • Based on the exception, the issue is what I described. You were trying to use constraint on something (self.view.superview) which was outside the tree of the view to which you added your constraint (_preView or self.view). Either add your constraint to self.view.superview or modify myConstraint to be between _preView and self.view and add constraint to self.view. – Hetal Vora Aug 05 '13 at 15:19
  • I'm not sure I understood your last line. Sorry but could you explain that in code? :( – Gaurav Wadhwani Aug 05 '13 at 16:41
  • Option 1 - If you thin your constraint should be with self.view: NSLayoutConstraint *myConstraint = [NSLayoutConstraint constraintWithItem:_preView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-239]; [self.view addConstraint:myConstraint]; – Hetal Vora Aug 05 '13 at 16:53
  • Option 2-if you think your constraint should be with self.view.superview then NSLayoutConstraint *myConstraint = [NSLayoutConstraint constraintWithItem:_preView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view.superView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-239]; [self.view.superView addConstraint:myConstraint]; – Hetal Vora Aug 05 '13 at 16:54
0

There are few things about Auto layouts. When ever you add layout constraints make sure it is not ambiguous. Ambiguous layout would result in undefined behaviour in your display. So good idea is to use IB which will never allow you to create a ambiguous layout, but you got to go through all the constraints to make sure they are valid.

If you want to do it programatically I would suggest you to use Visual language.

It will be helpful to go though these tips before using layout.

Vignesh
  • 10,205
  • 2
  • 35
  • 73