Center two views vertically using NSLayoutConstraint

21,118

Solution 1

I was able to do it by doing something like this:

NSNumber *sepHeight = @60.0F;

// Center the two views horizontally
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:imageView
                                                      attribute:NSLayoutAttributeCenterX
                                                      relatedBy:NSLayoutRelationEqual
                                                         toItem:self.view
                                                      attribute:NSLayoutAttributeCenterX
                                                     multiplier:1
                                                       constant:0]];

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:button
                                                      attribute:NSLayoutAttributeCenterX
                                                      relatedBy:NSLayoutRelationEqual
                                                         toItem:self.view
                                                      attribute:NSLayoutAttributeCenterX
                                                     multiplier:1
                                                       constant:0]];

// Position the two views one below the other, using the separator height defined above
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[imageview]-sepHeight-[button]"
                                                                  options:0
                                                                  metrics:NSDictionaryOfVariableBindings(sepHeight)
                                                                    views:views]];

// Force the button distance from the bottom to be the half of the size of the content
CGFloat constant = (imageview.frame.size.height + button.frame.size.height + [sepHeight floatValue]) / 2.0;
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:button
                                                      attribute:NSLayoutAttributeBottom
                                                      relatedBy:NSLayoutRelationEqual
                                                         toItem:self.view
                                                      attribute:NSLayoutAttributeCenterY
                                                     multiplier:1
                                                       constant:constant]];

The tricky part is the constant value. That value is the half of the height of all the views, including their separators. This means that, if the imageview's height is 360, the button's height is 70 and the separator is 60, that constant will be (360 + 70 + 60)/2 = 245.

There should be a smarter way, indeed, but for now I think this is ok.

Solution 2

There are two ways to approach this.

  1. As @uchuugaka suggests, place your imageview and button in a container view. You don't have the same centering problem if the subviews are pinned to the edges of the container (which you would size accordingly - height would be 360 + 30 + 70 == 460. Then you can center this container in your view with a simple align-center-Y constraint.

  2. You can use spacer views. By adding two hidden views you can specify constraints to position them above/below your imageview and button, with equal heights such they act as springs. Here's code that does this, using your dimensions:


- (void) viewDidLoad
{
    [super viewDidLoad];

    UIView* imageview = [UIView new];
    imageview.backgroundColor = [UIColor blueColor];
    imageview.translatesAutoresizingMaskIntoConstraints = NO;

    UIView* button = [UIView new];
    button.backgroundColor = [UIColor greenColor];
    button.translatesAutoresizingMaskIntoConstraints = NO;


    UIView* spacer1 = [UIView new];
    spacer1.backgroundColor = [[UIColor redColor] colorWithAlphaComponent: 0.5];
    spacer1.translatesAutoresizingMaskIntoConstraints = NO;
    spacer1.hidden = YES; // comment out to show spacer!

    UIView* spacer2 = [UIView new];
    spacer2.backgroundColor = [[UIColor redColor] colorWithAlphaComponent: 0.5];
    spacer2.translatesAutoresizingMaskIntoConstraints = NO;
    spacer2.hidden = YES; // comment out to show spacer!

    [self.view addSubview: imageview];
    [self.view addSubview: button];
    [self.view addSubview: spacer1];
    [self.view addSubview: spacer2];

    NSDictionary* views = NSDictionaryOfVariableBindings( imageview, button, spacer1, spacer2 );

    NSArray* constraints;
    constraints = [NSLayoutConstraint constraintsWithVisualFormat: @"V:|[spacer1(==spacer2)][imageview(360)]-30-[button(70)][spacer2(==spacer1)]|"
                                                          options: 0
                                                          metrics: nil
                                                            views: views];
    [self.view addConstraints: constraints];

    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:imageview
                                                          attribute:NSLayoutAttributeWidth
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:nil
                                                          attribute:NSLayoutAttributeNotAnAttribute
                                                         multiplier:1
                                                           constant:300]];

    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:button
                                                          attribute:NSLayoutAttributeWidth
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:nil
                                                          attribute:NSLayoutAttributeNotAnAttribute
                                                         multiplier:1
                                                           constant:210]];


    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:imageview
                                                          attribute:NSLayoutAttributeCenterX
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.view
                                                          attribute:NSLayoutAttributeCenterX
                                                         multiplier:1
                                                           constant:0]];

    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:button
                                                          attribute:NSLayoutAttributeCenterX
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.view
                                                          attribute:NSLayoutAttributeCenterX
                                                         multiplier:1
                                                           constant:0]];
}
Share:
21,118
Fabiano Francesconi
Author by

Fabiano Francesconi

Polyhedric. One of the few badass scientists that can relate to people. Self-taught musician. Big Data Engineering Manager. Currently employed at Teralytics

Updated on August 16, 2020

Comments

  • Fabiano Francesconi
    Fabiano Francesconi over 3 years

    Imagine this following scenario. You have a UIImageView and a UIButton. The first is 300x360, the second is 210x70. The imageview contains a catalog image, the button says "open catalog".

    I would like to position elements in the main view according to these requirements:

    • the two elements should be centered horizontally, namely the center.x coordinates should be all equal (view, image and button);

    • the two elements should be centered vertically in the following way: separator (flexible) - imageview - separator (fixed, let's say 30 pts) - button - separator (flexible). The topmost and bottommost separator should have the same size (this is the meaning of centered).

    I cannot make this to work using NSLayoutConstraint.

    So far, what I did, has been centering the X coordinate of the two elements using NSLayoutAttributeCenterX and NSLayoutRelationEqual to the same view attribute.

    The last part, according to my idea, is to fix their vertical alignment. I tried using @"V:|-[imageview(360)]-[button(70)]-|" but it won't work (Unable to simultaneously satisfy constraints.).

    If I use @"V:|-[imageview(360)]-[button]-|" I get everything partially ok. Namely, the top part is perfect but the button is stretched so to fill the gap between the internal separator and the bottom of the view.

    How can I make those elements to be fixed-sized and to let Auto-layout to just figure out how to place them in the view?