使用 NSLayoutConstraint 垂直居中两个视图

Posted

技术标签:

【中文标题】使用 NSLayoutConstraint 垂直居中两个视图【英文标题】:Center two views vertically using NSLayoutConstraint 【发布时间】:2013-06-25 10:20:21 【问题描述】:

想象一下以下场景。你有一个 UIImageView 和一个 UIButton。第一个是 300x360,第二个是 210x70。 imageview 包含目录图像,button 表示“打开目录”。

我想根据这些要求在主视图中定位元素:

两个元素应该水平居中,即center.x坐标应该全部相等(视图、图像和按钮);

这两个元素应该以下列方式垂直居中:分隔符(灵活)-图像视图-分隔符(固定,比如说 30 分)-按钮-分隔符(灵活)。最上面和最下面的分隔符应该大小一致(这就是居中的意思)。

我无法使用 NSLayoutConstraint 使其工作。

到目前为止,我所做的一直是使用 NSLayoutAttributeCenterXNSLayoutRelationEqual 将两个元素的 X 坐标居中到相同的 view 属性。

根据我的想法,最后一部分是修复它们的垂直对齐方式。 我尝试使用@"V:|-[imageview(360)]-[button(70)]-|",但它不起作用(Unable to simultaneously satisfy constraints.)。

如果我使用@"V:|-[imageview(360)]-[button]-|",我会得到部分内容。也就是说,顶部是完美的,但按钮被拉伸以填充内部分隔符和视图底部之间的间隙。

如何使这些元素固定大小并让自动布局来确定如何将它们放置在视图中?

【问题讨论】:

ios 9 中,看看 UIStackView 和 UILayoutGuide 来解决这些问题。 【参考方案1】:

我可以通过这样做:

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]];

棘手的部分是常量值。该值是所有视图高度的一半,包括它们的分隔符。这意味着,如果图像视图的高度为 360,按钮的高度为 70,分隔符为 60,则该常数将为 (360 + 70 + 60)/2 = 245。

确实应该有更聪明的方法,但现在我认为这还可以。

【讨论】:

聪明!使用 Auto Layout 思维的间接性,以及系数和常数的好例子。所以,描述它的一种方法是 Coefficient = 0.5 Constant = (imageHeightConstant + imageButtonVerticalSpaceConstant + buttonHeightConstant ) 或者我错过了什么......?常量可以存储为 CGFloat 变量或属性,并针对适当的大小进行调整... :) 不,乘数应用于 self.view.frame.origin.y 值,并将常数添加到该值。我现在将使用变量重写它。【参考方案2】:

有两种方法可以解决这个问题。

    正如@uchuugaka 建议的那样,将您的图像视图和按钮放在容器视图中。如果子视图被固定到容器的边缘(您可以相应地调整大小 - 高度为 360 + 30 + 70 == 460),则不会有相同的居中问题。然后您可以在视图中使用简单的 align-center-Y 约束。

    您可以使用间隔视图。通过添加两个隐藏视图,您可以指定约束以将它们定位在您的图像视图和按钮的上方/下方,具有相同的高度,以便它们充当弹簧。这是使用您的尺寸执行此操作的代码:


- (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]];

【讨论】:

我喜欢间隔视图的想法。除了它让我想起了 90 年代的 html 间隔 gif ;) 间隔视图或额外的容器视图,除非您事先知道(固定)高度。那好吧。 :)【参考方案3】:

您需要使垂直空间等于或大于图像视图顶部和按钮底部的空间。 但是,这听起来也像是简单地拥有包含图像和按钮的附加视图会更容易。然后在超级视图中将该视图 V 和 H 居中。

自动布局有时仍然意味着容器视图。

【讨论】:

应该没问题,但问题是如果你创建一个容器,你在容器中也会遇到同样的问题:) 一点也不。容器可以被限制在一个子视图的顶部和另一个子视图的底部。 你是对的。如果您将容器的高度设为所有高度(和分隔符)的总和,那么您将得到一个很好的解决方案。我不太喜欢有补充观点的想法,但我认为与我的相比,解决方案可能更容易理解。 我不能说一个更好。每个解决方案都有权衡。自动布局还很年轻。

以上是关于使用 NSLayoutConstraint 垂直居中两个视图的主要内容,如果未能解决你的问题,请参考以下文章

如何确定 NSLayoutConstraint 是水平的还是垂直的?

使用自动布局垂直居中子视图

如何在堆栈中垂直或水平居中小部件?

微信小程序实现新闻列表(文字垂直居上垂直居下)

使用绝对位置和百分比垂直居中子 div 时出现错误

css如何控制文字垂直居底