自动布局(约束):如何在父视图中将两个并排的视图居中

69
我正在尝试使用自动布局(iOS6)和约束来完成此操作。
基本上,我将我的大视图分成了底部的两个部分。在这些部分内(目前是子视图),我有一个图像视图和一个标签。我想让它们在两侧居中,并具有可变长度的文本。
我的头脑主要围绕着自动布局,但我不确定最佳方法是什么。我倾向于认为这在IB中不可能,但在代码中可以实现。
我将继续尝试解决这个问题,但同时,这里是我要创建的示例。

enter image description here

8个回答

67

这是您想要的内容吗?

短标签 长标签

我通过在leftSection中添加一个视图(命名为viewCenteredInLeftSection),然后将时钟图片和标签作为子视图添加,并使用以下约束来完成:

  • 使viewCenteredInLeftSection的CenterX和CenterY等于其父视图(即leftSection)。
  • 使clockImage的顶部、底部和前缘与其父视图(即viewCenteredInLeftSection)相等。
  • 使label的后缘与其父视图(即viewCenteredInLeftSection)相等。
  • 使clockImage的后缘与label的前缘相隔标准距离。

viewCenteredInLeftSection

我在Interface Builder中调整iOS UIView大小时遇到了困难,因此我制作了我的OS X示例,并且完全可以在Interface Builder中完成。如果您在Interface Builder中无法进行上述约束,请告诉我,我将发布可以创建它们的代码。

2014-08-26编辑

Luda,以下是Xcode 5的“Pin”和“Align”菜单,也可在Xcode的菜单栏中找到:

对齐菜单 固定菜单

下面是我在Interface Builder中做的示例。蓝色视图是原始问题中给定应该居中图片和标签的“父视图”。

我将绿色视图(我命名为 viewCenteredInLeftSection )作为 "父视图" 的子视图添加。 然后,我突出显示它,并使用“水平中心对齐容器”和“垂直中心对齐容器”的对齐菜单来创建约束以定义其位置。
我将时钟图像作为 viewCenteredInLeftSection 的子视图添加,并使用约束定义其宽度和高度。 我突出显示时钟图像和 viewCenteredInLeftSection,然后使用对齐>前缘、对齐>顶部边缘和对齐>底部边缘应用约束。
我将标签作为 viewCenteredInLeftSection 的子视图添加,并定位它离时钟图像的 标准 Aqua 空间距离。 我突出显示标签和 viewCenteredInLeftSection,然后使用对齐>尾缘应用约束。
使用 Xcode 5 的 Interface Builder 比 Xcode 4 更容易创建此内容。 Interface Builder

我发现添加一个封装的UIView比通过代码实现要容易得多。干得好! - capikaw
2
“将clockImage的顶部、底部和前导边与其父视图相等”是什么意思?这可以在Interface Builder中完成吗? - Michael Forrest
是的,从Xcode 5开始,这可以在Interface Builder中完成。在左侧的文档大纲中,选择两个视图(clockImage和其不可见的父视图)。然后单击Interface Builder的自动布局菜单中的对齐菜单,并使用复选框创建3个约束条件 - John Sauer
1
@JohnSauer 你可能是指“对齐->尾部边缘”(而不是尾部图像)。 - dariaa
1
这是唯一一个不需要编码的解决方案,即使对于宽度不相等的UI元素也可以工作。占位符视图是关键,而不是设置此视图的宽度,而是设置约束条件,以便根据其子元素调整其视图是我自己无法想出的唯一事情。谢谢! - Bruce
显示剩余4条评论

29

我找到了一种方法,而不需要添加另一个视图:

 [aView addConstraint:[NSLayoutConstraint constraintWithItem:viewOnLeft attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationLessThanOrEqual toItem:aView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
 [aView addConstraint:[NSLayoutConstraint constraintWithItem:viewOnRight attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationLessThanOrEqual toItem:aView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];

您还可以更改常量以在视图之间创建间隙。

  • 左侧视图约束常数:-X
  • 右侧视图约束常数:+X

居中子视图


1
没有包装器,只有“aView”,它是包含白球和计数器的矩形。 “viewOnLeft”是白球,“viewOnRight”是计数器。 - Lucien
啊,我明白你的意思了。好的,那是一个相当不错的解决方案。现在我在想,你甚至可以在代码中不使用“aView”包装器来完成它。 - Bob Spryn
16
这似乎将左视图的右侧和右视图的左侧与它们的容器中心对齐。如果左侧和右侧视图的宽度大致相同,则看起来很好。但是,如果一侧比另一侧宽得多,那么它肯定会看起来不平衡。 - EthanB
这绝对是最好的方法。我喜欢。 - Samuel Goodwin
虽然我在测试中运行了这个代码,它看起来是“可行”的,但我认为它会导致布局不明确。如果您暂停应用程序并在调试器中运行此命令 po [[UIWindow keyWindow] _autolayoutTrace],LLDB 将告诉您是否存在布局不明确的情况。 - mon4goos
显示剩余3条评论

17

斯坦福大学在iOS 7的讲座中提供了一种解决方案,该方案非常有效。在此附上该解决方案。(这里 sdfssfg... 的东西是 label1,efsdfg.... 的东西是 label2)

输入图像描述


1
能否提供更多的解释? - Tommy
2
基本上,将所需的两个标签(假设为L1和L2)附加到两个透明的UIViews(假设为V1和V2)中。 V1和V2应具有相等的宽度,并且所有四个视图都应对齐为:-V1-L1-L2-V2-。这里的连字符是它们之间的水平距离(约束),可以是任何值。保持为零,我会建议。 - Vinayak Parmar
我怎样才能找到那个讲座?它的编号是多少? - Dmitry L.
6
请查看这个视频https://www.youtube.com/watch?v=pv1EHGEf884(在第48分钟后,他将解释上述问题)。 - Vinayak Parmar
这是有关编程的内容,请将以下文本从英语翻译成中文。仅返回已翻译的文本:更简化的答案在此处提供示例https://dev59.com/PGUp5IYBdhLWcg3wAj3y#38657450 - Dashrath

16

花了一点时间,但我找到了一个相当可靠的解决方案。我发现了John Sauer提供的相同解决方案,但不想再添加一个视图来包装这些内容。

答案需要三个步骤。

1)包含其他两个子视图的子视图(我称之为leftInfoSection)的宽度需要由其内容确定。这就避免了需要将其左右约束到父视图(或其他视图)以确定其宽度的需求。其中很重要的一点是让宽度由子视图定义。

enter image description here

2)我仍然需要在IB中拥有一个前导约束,以使其具有有效布局。(它需要知道如何水平放置leftInfoSection)。将那个约束连接到你的代码中,这样你就可以删除它。除此之外,我还有一个大于等于垂直分隔线+3的尾部约束。

3)最后一个关键是考虑用于工作的信息(在代码中,因为IB受限制)。我意识到我知道上面我的部分的水平分隔符的中心,并且我的leftInfoSection的中心将是该水平条的中心减去1/4的水平条宽度。以下是左侧和右侧的最终代码:

// remove the unwanted constraint to the right side of the thumbnail
[self.questionBox removeConstraint:self.leftInfoViewLeadingConstraint];
// calculate the center of the leftInfoView
CGFloat left = self.horizontalDividerImageView.frame.size.width/4 * -1;
// constrain the center of the leftInfoView to the horizontal bar center minus a quarter of it to center things
[self.questionBox addConstraint:[NSLayoutConstraint constraintWithItem:self.leftInfoView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.horizontalDividerImageView attribute:NSLayoutAttributeCenterX multiplier:1 constant:left]];

// remove the unwanted constraint to the right side of the questionBox
[self.questionBox removeConstraint:self.rightInfoViewTrailingConstraint];
// calculate the center of the rightInfoView
CGFloat right = left * -1;
// constrain the center of the rightInfoView to the horizontal bar center plus a quarter of it to center things
[self.questionBox addConstraint:[NSLayoutConstraint constraintWithItem:self.rightInfoView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.horizontalDividerImageView attribute:NSLayoutAttributeCenterX multiplier:1 constant:right]];

结果: 最终结果

此外,IB 在自动更新约束方面可能非常烦人。当我试图将子视图的前导和尾随约束定义为0时,它会不断地断开其中一个,并对超级视图进行约束以定义宽度。诀窍是将那个不需要的约束暂时保留在原地,但将其优先级降低到999。然后我就可以创建子视图约束来定义宽度。


11

这个方案效果不错,但需要使用2个空白的UIView:

UIView *spacer1 = [[UIView alloc] init];
spacer1.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:spacer1];

UIView *spacer2 = [[UIView alloc] init];
spacer2.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:spacer2];

NSDictionary *views = NSDictionaryOfVariableBindings(spacer1, spacer2, imageView, label);

[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[spacer1(>=0)][imageView]-4-[label][spacer2(==spacer1)]|" options:0 metrics:nil views:views];

for (int i = 0; i < constraintsArray.count; i++) {

    [self.view addConstraint:constraintsArray[i]];
}

顺便说一句,我尝试过仅通过添加约束imageView.leading = label.trailing来实现此功能,但并未成功。可能是我太蠢了。 - Tommy

6
自iOS 9之后,另一种实现此功能的方法是使用“Stack Views”(堆栈视图)。

3
支持iOS 9.0及以上版本。 - Dmitry Kurilo
6
它的一个例子会是一个更好的答案。 - Dashrath

0

有几种方法可以做到这一点。简单来说,以下是如何居中1..n个项目的步骤,假设所有项目都具有约束大小并且不会增长

  1. 在您的项目两侧放置2个间隔块。将间隔块锚定到父边缘。将您的第一个和最后一个项目锚定到这些间隔块上。最后,分配1个间隔块具有另一个间隔块的宽度。您不需要显式设置任何间隔块大小,因为它会被“解决”。

    • 间隔块1 -> left=parent:left width=间隔块2:width
    • 间隔块2 -> right=parent:right
    • 您的第一个项目 -> left=间隔块1:right
    • 您的最后一个项目 -> right=间隔块2:left
  2. 如果您不喜欢使用间隔块,并且您的项目数量是奇数,请将中间的项目居中对齐到父级中心。同时,请确保第一个和最后一个项目没有锚定到父边缘。

    • 您的中间项目 = centerX=parent:centerX
    • 其他项目->您的中间项目<-其他项目
  3. 如果您不喜欢使用间隔块,并且您的项目数量是偶数,请将2个内部项目的边缘居中对齐到父级中心。同时,请确保第一个和最后一个项目没有锚定到父边缘。

    • 左侧中间项目 -> right=parent:centerX
    • 右侧中间项目 -> left=parent:centerX
    • 其他项目->左侧中间项目 右侧中间项目<-其他项目
  4. 您还可以在中心处居中一个不可见的占位符,并将其锚定到该位置,但是在约束时仍需要考虑奇数/偶数项目,因此我不建议使用这种方法。


-1
你可能想要参考一下这个链接:

基于百分比的标记

基本上,它说的是首先使用一些不正确的边距,然后相对于其父视图进行更正。
在我的情况下运作良好。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接