pajevic
pajevic

Reputation: 4667

Wrong origins when adding subviews programmatically to UIView using autolayout

I have a UIView subclass where I programmatically create and add some subviews. To be exact, I have 6 subviews aligned horizontally next to each other with zero space between them. The side margins (i.e. the distance from the left border of my view to the first subview and the distance from the last subview to the right border of my view) must be 16pt and the remaining space must be distributed equally among the subviews. It should look like this:

enter image description here

I am trying to accomplish this using autolayout like this:

NSDictionary *viewsDict = @{@"digit1":digit1, @"digit2":digit2, @"digit3":digit3, @"digit4":digit4, @"digit5":digit5, @"digit6":digit6};
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-16-[digit1][digit2(==digit1)][digit3(==digit1)][digit4(==digit1)][digit5(==digit1)][digit6(==digit1)]-16-|" options:0 metrics:nil views:viewsDict]];

When I run this on iPhone 5 with 320pt screen view I expect the following result:

Subview width: (320 - 2*16) / 6 = 48

So the frames should be:

digit1: x =  16, width = 48
digit2: x =  64, width = 48
digit3: x = 112, width = 48
digit4: x = 160, width = 48
digit5: x = 208, width = 48
digit6: x = 256, width = 48

However, at runtime a strange thing happens: my margins are wrong:

enter image description here

The subview frames I get are these:

digit1: x =  21, width = 48
digit2: x =  66, width = 48
digit3: x = 111, width = 48
digit4: x = 156, width = 48
digit5: x = 201, width = 48
digit6: x = 246, width = 48

So my left margin is 21pt and the right is 26pt. Also, the subviews overlap by 3pt.

I have inspected the view hierarchy with Reveal and I can confirm that there is a constraint for the margins with the correct constant of 16. But the frame is for some reason different. There also don't seem to be any other conflicting constraints, nor do I get an autolayout error when I run the app.

Can anyone see what I am missing here?

EDIT:

Testing out Aloks suggestion with using spacer views in stead of spacing constraints I tried this:

UIView *spacer1 = [[UIView alloc] init];
UIView *spacer2 = [[UIView alloc] init];
spacer1.translatesAutoresizingMaskIntoConstraints = NO;
spacer2.translatesAutoresizingMaskIntoConstraints = NO;
spacer1.backgroundColor = [UIColor redColor];
spacer2.backgroundColor = [UIColor redColor];
[self addSubview:spacer1];
[self addSubview:spacer2];
NSDictionary *viewsDict = @{@"spacer1":spacer1, @"digit1":digit1, @"digit2":digit2, @"digit3":digit3, @"digit4":digit4, @"digit5":digit5, @"digit6":digit6, @"spacer2":spacer2};
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[spacer1(16)][digit1][digit2(==digit1)][digit3(==digit1)][digit4(==digit1)][digit5(==digit1)][digit6(==digit1)][spacer2(16)]|" options:0 metrics:nil views:viewsDict]];

This gives me the following result:

enter image description here

As you can see, the spacer views are positioned correctly, but the digit subviews remain where they were.

Upvotes: 2

Views: 534

Answers (3)

pajevic
pajevic

Reputation: 4667

I've finally solved my issue, and the problem was actually elsewhere. I am not the only developer on this project and I hadn't realized (it's a big class) that someone else had been setting the position of the subviews manually in the layoutSubviews method, like this:

- (void)layoutSubviews {
    [super layoutSubviews];

    // set center for each digit view
}

And of course, since the position was set after [super layoutSubviews] it changed the positions set by my autolayout constraints.

I want to thank you all who have contributed here. Your solutions helped me confirm that my original solution was in fact correct and forced me to look for the problem elsewhere.

Upvotes: 0

maddy
maddy

Reputation: 4111

The way you have used 16 as leading and trailing, instead make spacer1 (UIView *spacer1) and spacer2 (UIView *spacer2).

write your VFL this way:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    UIView *spacer1 = [[UIView alloc] init];
    UIView *spacer2 = [[UIView alloc] init];
    spacer1.translatesAutoresizingMaskIntoConstraints = NO;
    spacer2.translatesAutoresizingMaskIntoConstraints = NO;
    spacer1.backgroundColor = [UIColor redColor];
    spacer2.backgroundColor = [UIColor redColor];
    [self.view addSubview:spacer1];
    [self.view addSubview:spacer2];

    UIView *digit1 = [UIView new];
    digit1.backgroundColor = [UIColor yellowColor];
    digit1.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:digit1];

    UIView *digit2 = [UIView new];
    digit2.backgroundColor = [UIColor magentaColor];
    digit2.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:digit2];

    UIView *digit3 = [UIView new];
    digit3.backgroundColor = [UIColor lightGrayColor];

    digit3.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:digit3];

    UIView *digit4 = [UIView new];
    digit4.backgroundColor = [UIColor darkGrayColor];

    digit4.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:digit4];

    UIView *digit5 = [UIView new];
    digit5.backgroundColor = [UIColor blueColor];

    digit5.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:digit5];

    UIView *digit6 = [UIView new];
    digit6.backgroundColor = [UIColor brownColor];

    digit6.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:digit6];

    NSDictionary *viewsDict = @{@"spacer1":spacer1, @"digit1":digit1, @"digit2":digit2, @"digit3":digit3, @"digit4":digit4, @"digit5":digit5, @"digit6":digit6, @"spacer2":spacer2};
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[spacer1(16)][digit1][digit2(==digit1)][digit3(==digit1)][digit4(==digit1)][digit5(==digit1)][digit6(==digit1)][spacer2(16)]|" options:0 metrics:nil views:viewsDict]];

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

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


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


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


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


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


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


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

}

it should work.

OUTPUT

Upvotes: 1

Ketan Parmar
Ketan Parmar

Reputation: 27448

Your constraints should be like :

equal width to every views with each other

fixed height to every views

leading,trailing and top or bottom (which is appropriate) to every view.

And it will work fine

See the output of this constraints

enter image description here

You just need to convert it in code!!!!

and if you are adding views programmatically then how you are setting view's frame is also matter. Your frame should be dynamic as per screen size.

Hope this will help :)

Upvotes: 1

Related Questions