Autolayout issue

1.1k views Asked by At

I am developing an application in which i am using auto layout. I am following the following steps :

  1. step 1 : create a button in viewDidLoad

    [super viewDidLoad];
    
    self.view.translatesAutoresizingMaskIntoConstraints = NO;
    
    _button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    _button1.translatesAutoresizingMaskIntoConstraints = NO;
    [_button1 setTitle:@"B" forState:UIControlStateNormal];
    [self.view addSubview:_button1];
    
  2. step 2 : implement constraints in updateViewConstraints method

    [super updateViewConstraints];
    
    [self.view removeConstraints:self.view.constraints];
    
    if (UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation))
    {
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0f constant:100.0f];
        [self.view addConstraint:constraint];
    
        NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0f constant:-100.0f];
        [self.view addConstraint:constraint1];
    
        NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f constant:200.0f];
        [self.view addConstraint:constraint2];
    
        NSLayoutConstraint *constraint3 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0f constant:-100.0f];
        [self.view addConstraint:constraint3];
    
    
        _button1.backgroundColor = [UIColor redColor];
    } else{
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0f constant:200.0f];
        [self.view addConstraint:constraint];
    
        NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0f constant:-200];
        [self.view addConstraint:constraint1];
    
        NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f constant:50.0f];
        [self.view addConstraint:constraint2];
    
        NSLayoutConstraint *constraint3 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0f constant:-50.0f];
        [self.view addConstraint:constraint3];
    
        _button1.backgroundColor = [UIColor blueColor];
    }
    

but when i switch the device orientation, the console prints the following :

Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) ( "UIView:0x8a461c0 (Names: '|':UIWindow:0x8a42970 )>", "", "", "UIButton:0x8a45ea0 (Names: '|':UIView:0x8a461c0 )>", "" )

Will attempt to recover by breaking constraint

Break on objc_exception_throw to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in may also be helpful.

could anyone please tell me what is wrong with this layout ?

5

There are 5 answers

7
Rob On BEST ANSWER

The issue is that you're calling [super updateViewConstraints] in updateViewConstraints while you still have constraints in place for the button. So, as you transition from landscape to portrait, you still have the landscape button constraints (which are unsatisfiable in portrait), but are asking the main view to update its (portrait) constraints. If you move the call to [super updateViewConstraints] anywhere after you remove all of your existing button constraints, and you should be in good shape.


A couple of asides:

  1. If using storyboards/NIBS, you should remove the line that says:

    self.view.translatesAutoresizingMaskIntoConstraints = NO;
    

    But keep the line that says:

    _button1.translatesAutoresizingMaskIntoConstraints = NO;
    
  2. I'd be wary of a wholesale removal of all constraints. I usually keep arrays of the constraints I want to remove, and that way I can easily remove just the ones that I need removing and will be reconstructing. In your case, removing all is probably fine, but as you add more and more constraints to your view, it's probably just easier to keep track of which you want to remove and reconstruct:

    @property (nonatomic, strong) NSArray *verticalConstraints;
    @property (nonatomic, strong) NSArray *horizontalConstraints;
    
  3. I might suggest using VFL, which is a little more concise:

    - (void)updateViewConstraints
    {
        if (self.horizontalConstraints)
            [self.view removeConstraints:self.horizontalConstraints];
    
        if (self.verticalConstraints)
            [self.view removeConstraints:self.verticalConstraints];
    
        [super updateViewConstraints];
    
        NSDictionary *views   = NSDictionaryOfVariableBindings(_button1);            
        NSDictionary *metrics = nil;
    
        if (UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation))
        {
            metrics = @{@"left"   : @100,
                        @"right"  : @100,
                        @"top"    : @200,
                        @"bottom" : @100};
    
            _button1.backgroundColor = [UIColor redColor];
        } else{
            metrics = @{@"left"   : @200,
                        @"right"  : @200,
                        @"top"    : @50,
                        @"bottom" : @50};
    
            _button1.backgroundColor = [UIColor blueColor];
        }
    
        self.horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(left)-[_button1]-(right)-|" options:0 metrics:metrics views:views];
        self.verticalConstraints   = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(top)-[_button1]-(bottom)-|" options:0 metrics:metrics views:views];
        [self.view addConstraints:self.horizontalConstraints];
        [self.view addConstraints:self.verticalConstraints];
    }
    
1
Brad Allred On

Typically layout constraints are built in IB and then adjusted on orientation change, not discarding and recreating constraints on orientation change as you seem to want to do.

Anyway, the problem looks to be that you are not removing all the required constraints. the line [self.view removeConstraints:self.view.constraints]; only removes constraints the views own constraints and ignores the fact that there are probably constraints on other views (i.e. the superview) relating to view.

I don't know for sure if this is your problem, but I would try and adjust existing constraints instead and see if that fixes the problem. You can make IBOutlets for layout constraints if that will help you.

0
rdelmar On

This can also be done without checking on the orientation by using both the multiplier and constant values of the constraint to create a single constraint (for each direction) that works for both portrait and landscape (If you make the view in the storyboard, you need to remove any constraints you made there before adding these -- you can have that done automatically by checking the "Placeholder - Remove at build time" box in the attributes inspector for each of the constraints you want removed). In you particular case, I think these values work:

- (void)viewDidLoad {
    [super viewDidLoad];

    _button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    _button1.translatesAutoresizingMaskIntoConstraints = NO;
    [_button1 setTitle:@"B" forState:UIControlStateNormal];
    [self.view addSubview:_button1];
    NSLayoutConstraint *topCon = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeTop relatedBy:0 toItem:self.view attribute:NSLayoutAttributeBottom multiplier:.9375 constant:-250];
    NSLayoutConstraint *bottomCon = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeBottom relatedBy:0 toItem:self.view attribute:NSLayoutAttributeBottom multiplier:.6875 constant:50];
    NSLayoutConstraint *leftCon = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeLeft  relatedBy:0 toItem:self.view attribute:NSLayoutAttributeRight multiplier:.625 constant:-100];
    NSLayoutConstraint *rightCon = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeRight relatedBy:0 toItem:self.view attribute:NSLayoutAttributeRight multiplier:.375 constant:100];
    [self.view addConstraints:@[topCon,bottomCon,rightCon,leftCon]];
}

Notice that the attribute for self.view is bottom for the top constraint, and right for the left constraint. When using the multiplier, you have to do it this way, since the left and top attribute values are zero, so multiplying by anything would be useless.

Calculating these values by hand is a pain, so I don't actually set them up that way. Instead, I've written a category on NSLayoutConstraint that allows me to set up the constraints like this (an exampleProject with the category can be found at http://jmp.sh/v/fgHhRNX2twlrgG338CDz):

[self.view addConstraint:[NSLayoutConstraint rightConstraintForView:_button1 viewAttribute:NSLayoutAttributeRight superview:self.view portraitValue:100 landscapeValue:200]];
[self.view addConstraint:[NSLayoutConstraint topConstraintForView:_button1 viewAttribute:NSLayoutAttributeTop superview:self.view portraitValue:200 landscapeValue:50]];
[self.view addConstraint:[NSLayoutConstraint bottomConstraintForView:_button1 viewAttribute:NSLayoutAttributeBottom superview:self.view portraitValue:100 landscapeValue:50]];
[self.view addConstraint:[NSLayoutConstraint leftConstraintForView:_button1 viewAttribute:NSLayoutAttributeLeft superview:self.view portraitValue:100 landscapeValue:200]];
3
matths On

I copy & pasted your stuff into a completely fresh project and it works fine. So you probably have something more in your project which might interfere. Are you using Storyboards?

#import "DemoViewController.h"

@interface DemoViewController()

@property (nonatomic, strong) UIButton *button1;

@end

@implementation DemoViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.view.translatesAutoresizingMaskIntoConstraints = NO;

    _button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    _button1.translatesAutoresizingMaskIntoConstraints = NO;
    [_button1 setTitle:@"B" forState:UIControlStateNormal];
    [self.view addSubview:_button1];
}

- (void)updateViewConstraints
{
    [super updateViewConstraints];

    [self.view removeConstraints:self.view.constraints];

    if (UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation))
    {
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0f constant:100.0f];
        [self.view addConstraint:constraint];

        NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0f constant:-100.0f];
        [self.view addConstraint:constraint1];

        NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f constant:200.0f];
        [self.view addConstraint:constraint2];

        NSLayoutConstraint *constraint3 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0f constant:-100.0f];
        [self.view addConstraint:constraint3];


        _button1.backgroundColor = [UIColor redColor];
    } else{
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0f constant:200.0f];
        [self.view addConstraint:constraint];

        NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0f constant:-200];
        [self.view addConstraint:constraint1];

        NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f constant:50.0f];
        [self.view addConstraint:constraint2];

        NSLayoutConstraint *constraint3 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0f constant:-50.0f];
        [self.view addConstraint:constraint3];

        _button1.backgroundColor = [UIColor blueColor];
    }
}

@end

and the AppDelegate:

#import "AppDelegate.h"
#import "DemoViewController.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    self.window.rootViewController = [[DemoViewController alloc] init];
    [self.window makeKeyAndVisible];
    return YES;
}

@end
2
darren102 On

If you remove this line

self.view.translatesAutoresizingMaskIntoConstraints = NO;

Then i believe you should not longer have the issue, i have seen it a few times where if you use the storyboards then adding this line of code will cause these types of issues to appear when using the application.