Search code examples
iosobjective-ciphonecocoa-touchautolayout

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.


Solution

  • 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.