9

If I have a view in my app with 3 subviews (as an example) with the following spacing between them:

TOP - 40 points - SUBVIEW 1 - 60 points - SUBVIEW 2 - 80 points - SUBVIEW 3 - 80 points - BOTTOM

I understand how I can use auto layout to ensure each of my subviews keeps it's height and width, and I can get everything to be aligned on either a 3.5 OR 4 inch iPhone screen.


But I can't work out what kind of constraint I need to make it so that if it were aligned for a 3.5" screen, and then went to a 4" screen, each spacing would increase proportionally (e.g. 40 points would go to 47, 60 to 71, 80 to 95 - or thereabouts).

Is this possible? Or do I need to make all the spacing equal between the elements? (If so how would I still get it to resize equally?)


I'm new to Auto Layout so if I've missed anything off, or haven't made it clear what I means please let me know, thank you.

Jon Cox
  • 10,622
  • 22
  • 78
  • 123

2 Answers2

11

The Trick is to add not only one but TWO constraints between your views. One with "Greater Than or Equal" and one with "Less Then or Equal". The minimum size (Greater Then or Equal) should be the spacing on the 3.5" display. The maximum size (Less Then or Equal" should be the spacing on the 4" display.

Example:

TOP - 40 points - SUBVIEW 1 - 60 points - SUBVIEW 2 - 80 points - SUBVIEW 3 - 80 points - BOTTOM

TOP - SUBVIEW1: Select both in Interface Builder. Add the constraint "Vertical Spacing" two times. Set one to Greater Then or Equal 40. Set the other to Lesser Then or Equal 47.

The sum of all Greater Then values + all heights of your views should be 480 pixel (3.5") The sum of all Lesser Then values + all heights of your views should be 568 pixel (4")

sust86
  • 1,870
  • 2
  • 18
  • 25
1

I don't know any easy way to do this. I made one where all the spaces were equal, but to do that, I had to fill the spaces with invisible labels (just label with no title). So, I had my 4 visible objects and the 5 "spacer labels" all right on top of each other. I made the 4 visible objects all have explicit heights, and the spacers with no fixed heights, but set to be all the same:

-(void)viewDidLoad {
    [super viewDidLoad];

    NSMutableDictionary *viewsDict = [NSMutableDictionary dictionary];
    for (int i=1; i<5; i++) { // Labels with titles
        UILabel *b = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 150, 44)];
         b.text = @"This is my label";
        [b setTranslatesAutoresizingMaskIntoConstraints:NO];
        [viewsDict setObject:b forKey:[NSString stringWithFormat:@"b%d",i]];
    }

    for (int i=1; i<6; i++) { // Spacer labels
        UILabel *l = [[UILabel alloc ]init];
        [l setTranslatesAutoresizingMaskIntoConstraints:NO];
        [viewsDict setObject:l forKey:[NSString stringWithFormat:@"l%d",i]];
    }

    for (id obj in viewsDict.allKeys) 
        [self.view addSubview:viewsDict[obj]];

    NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[l1][b1][l2(==l1)][b2][l3(==l1)][b3][l4(==l1)][b4][l5(==l1)]|"
                                                                   options:NSLayoutFormatAlignAllLeading
                                                                   metrics:nil
                                                                     views:viewsDict];


    NSArray *constraints2 = [NSLayoutConstraint constraintsWithVisualFormat:@"|-[b1]"
                                                                   options:0
                                                                   metrics:nil
                                                                     views:viewsDict];


    [self.view addConstraints:constraints];
    [self.view addConstraints:constraints2];
} 

To make the spaces different, I think you have to use the longer form of expressing constraints, rather than the visual format. The below code seems to work for me. I'm using the same definition of the views as above, except that I cut the number of titled labels to 3 and spacers to 4 to match you question. The relative spacing should be as in your example, 2:3:4:4.

NSLayoutConstraint *con1 = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeTop relatedBy:0 toItem:viewsDict[@"l1"] attribute:NSLayoutAttributeTop multiplier:1 constant:0];
    NSLayoutConstraint *con2 = [NSLayoutConstraint constraintWithItem:viewsDict[@"l1"] attribute:NSLayoutAttributeBottom relatedBy:0 toItem:viewsDict[@"b1"] attribute:NSLayoutAttributeTop multiplier:1 constant:0];
    NSLayoutConstraint *con3 = [NSLayoutConstraint constraintWithItem:viewsDict[@"b1"] attribute:NSLayoutAttributeBottom relatedBy:0 toItem:viewsDict[@"l2"] attribute:NSLayoutAttributeTop multiplier:1 constant:0];
    NSLayoutConstraint *con4 = [NSLayoutConstraint constraintWithItem:viewsDict[@"l2"] attribute:NSLayoutAttributeBottom relatedBy:0 toItem:viewsDict[@"b2"] attribute:NSLayoutAttributeTop multiplier:1 constant:0];
    NSLayoutConstraint *con5 = [NSLayoutConstraint constraintWithItem:viewsDict[@"b2"] attribute:NSLayoutAttributeBottom relatedBy:0 toItem:viewsDict[@"l3"] attribute:NSLayoutAttributeTop multiplier:1 constant:0];
    NSLayoutConstraint *con6 = [NSLayoutConstraint constraintWithItem:viewsDict[@"l3"] attribute:NSLayoutAttributeBottom relatedBy:0 toItem:viewsDict[@"b3"] attribute:NSLayoutAttributeTop multiplier:1 constant:0];
    NSLayoutConstraint *con7 = [NSLayoutConstraint constraintWithItem:viewsDict[@"b3"] attribute:NSLayoutAttributeBottom relatedBy:0 toItem:viewsDict[@"l4"] attribute:NSLayoutAttributeTop multiplier:1 constant:0];
    NSLayoutConstraint *con8 = [NSLayoutConstraint constraintWithItem:viewsDict[@"l4"] attribute:NSLayoutAttributeBottom relatedBy:0 toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
    NSLayoutConstraint *con9 = [NSLayoutConstraint constraintWithItem:viewsDict[@"l1"] attribute:NSLayoutAttributeHeight relatedBy:0 toItem:viewsDict[@"l2"] attribute:NSLayoutAttributeHeight multiplier:.66 constant:0];
    NSLayoutConstraint *con10 = [NSLayoutConstraint constraintWithItem:viewsDict[@"l1"] attribute:NSLayoutAttributeHeight relatedBy:0 toItem:viewsDict[@"l3"] attribute:NSLayoutAttributeHeight multiplier:.5 constant:0];
    NSLayoutConstraint *con11 = [NSLayoutConstraint constraintWithItem:viewsDict[@"l1"] attribute:NSLayoutAttributeHeight relatedBy:0 toItem:viewsDict[@"l4"] attribute:NSLayoutAttributeHeight multiplier:.5 constant:0];
    NSLayoutConstraint *con12 = [NSLayoutConstraint constraintWithItem:viewsDict[@"b1"] attribute:NSLayoutAttributeLeading relatedBy:0 toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:100];
    NSLayoutConstraint *con13 = [NSLayoutConstraint constraintWithItem:viewsDict[@"b2"] attribute:NSLayoutAttributeLeading relatedBy:0 toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:100];
    NSLayoutConstraint *con14 = [NSLayoutConstraint constraintWithItem:viewsDict[@"b3"] attribute:NSLayoutAttributeLeading relatedBy:0 toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:100];

    NSArray *constraints = @[con1,con2,con3,con4,con5,con6,con7,con8,con9,con10,con11,con12,con13,con14];
    [self.view addConstraints:constraints];
rdelmar
  • 103,982
  • 12
  • 207
  • 218
  • Great, thanks for the idea. I shall try it out and see if I can hack something together that approaches what I want :) – Jon Cox Jan 07 '13 at 21:13
  • After quite a bit of learning how to use Auto Layout I've successfully managed to use your idea. Thank you! – Jon Cox Jan 08 '13 at 12:46
  • A word of advice to anyone else trying this, check there aren't any automatically added height constraints - as this could result in an overall height greater than the screen size on rotation. If there are, you can simply reduce their priority to less than the height constraints you want to be applied. – Jon Cox Jan 08 '13 at 12:47
  • Another word of advice, I achieved this working in Storyboards - i.e. graphically not by code. – Jon Cox Jan 08 '13 at 12:48