Center two views vertically using NSLayoutConstraint
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.
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.
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]];
}
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, 2020Comments
-
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, thebutton
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
andNSLayoutRelationEqual
to the sameview
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?