Reckoner Reckoner - 3 months ago 21
iOS Question

AutoLayout programmatically doesn't work

I want to go into the depths of auto layout and so I want to do it programmatically in order to understand it properly.I know how to use it through storyboard pretty well.Now here is what I am doing(in

viewDidAppear:
)

-(void)scenario2{

PGView *viewA = [[PGView alloc]initWithFrame:CGRectMake(100, 100, 100, 100) andBackgroundColor:[UIColor blackColor]];
[self.view addSubview:viewA];

PGView *viewB = [[PGView alloc]initWithFrame:CGRectMake(200, 300, 100, 100) andBackgroundColor:[UIColor yellowColor]];
[self.view addSubview:viewB];

viewA.translatesAutoresizingMaskIntoConstraints = NO;
viewB.translatesAutoresizingMaskIntoConstraints = NO;

NSLayoutConstraint *constraint;

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[viewB(==100)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewB)]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[viewB(==100)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewB)]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[viewA(==200)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewA)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[viewA(==200)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewA)]];

//problem statement
constraint = [NSLayoutConstraint constraintWithItem:viewA attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:viewB attribute:NSLayoutAttributeWidth multiplier: 3.0f constant: 0.0f];
[self.view addConstraint:constraint];

NSLog(@"%@",viewA);
NSLog(@"%@",viewB);

}



  1. So here I have initialized two views with different colors.

  2. I know I need to set the property.
    translatesAutoresizingMaskIntoConstraints
    to
    NO
    before applying any new constraints. So I have done that.

  3. Now I am specifying heights and widths of both the views.So,the x_position and y_position for both the views are 0.I run the code and I get the expected result. Both views have the specified height and width at point (0,0).



Problem


  1. Now when I am writing the statement "problem statement" to adjust the width of viewB according to the width of viewA,its not working.

  2. Also, when I print views viewA and viewB,it prints the frames as (0,0,0,0).



Can anyone explain me this behavior?




Edit:Here are some modifications that I have made.I have checked the ambiguity of both the views using 'hasAmbiguousLayout' and its working fine now.Now what should be the next step if I want to resize viewB's width thrice to that of viewA's width?

-(void)scenario2{

PGView *viewA = [PGView new];
viewA.backgroundColor = [UIColor yellowColor];
[self.view addSubview:viewA];

PGView *viewB = [PGView new];
viewB.backgroundColor = [UIColor blackColor];
[self.view addSubview:viewB];

viewA.translatesAutoresizingMaskIntoConstraints = NO;
viewB.translatesAutoresizingMaskIntoConstraints = NO;

NSLayoutConstraint *constraint;

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[viewB(==100)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewB)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[viewB(==100)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewB)]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[viewA(==200)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewA)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[viewA(==200)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewA)]];


NSLog(@"%d",viewA.hasAmbiguousLayout);
NSLog(@"%d",viewB.hasAmbiguousLayout);

NSLog(@"%@",viewA);
NSLog(@"%@",viewB);

}

Rob Rob
Answer

The problem is that you've explicitly set width constraints for A and B to be 200 and 100, respectively. So the additional constraint that specifies that one should have a width of three times the other one is unsatisfiable when combined with the other constraints.

Furthermore, the constraints are ambiguous, as you've defined width and height constraints, but haven't defined where the two frames should be. Yes, you've defined the frame for these two views, but that is ignored when you apply the constraints. You probably shouldn't define the frame values at all, as it's misleading and confusing. But you should set, for example, the leading and top constraints, or specify those in the VFL, or add centerX and centerY constraints. You just want some constraints to dictate where the views should be placed and there's lots of ways to specify that.

And the reason that you're not seeing reasonable frame values at the end is that you've defined the constraints, but they haven't been applied yet. You could examine them in viewDidLayoutSubviews, if you wanted.


You might do something like:

- (void)scenario2 {

    PGView *viewA = [PGView new];
    viewA.backgroundColor = [UIColor yellowColor];
    [self.view addSubview:viewA];

    PGView *viewB = [PGView new];
    viewB.backgroundColor = [UIColor blackColor];
    [self.view addSubview:viewB];

    viewA.translatesAutoresizingMaskIntoConstraints = NO;
    viewB.translatesAutoresizingMaskIntoConstraints = NO;

    NSDictionary *views = NSDictionaryOfVariableBindings(viewA, viewB);

    // note, I added `|-100-` to say it's 100 points from the top (and 100 tall)

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[viewB(==100)]" options:0 metrics:nil views:views]];

    // likewise, to say 200 points from the left (and 100 wide)

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-200-[viewB(==100)]" options:0 metrics:nil views:views]];

    // again, 100 from the top (as well as 200 tall)

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[viewA(==200)]" options:0 metrics:nil views:views]];

    // and 100 from the left
    // but I've removed width definition, as we'll use your multiplier constraint below

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-100-[viewA]" options:0 metrics:nil views:views]];

    // now that we've removed the width constraint from the above,
    // this should now work fine

    NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:viewA attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:viewB attribute:NSLayoutAttributeWidth multiplier: 3.0f constant: 0.0f];
    [self.view addConstraint:constraint];

    // no point in doing this, because constraints haven't yet been applied
    //
    // NSLog(@"%@",viewA);
    // NSLog(@"%@",viewB);
}

// if you want to see where they are, you could do that here:

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    for (UIView *view in self.view.subviews) {
        NSLog(@"%@", view);
    }
    NSLog(@"-----");
}
Comments