在容器视图中均匀间隔多个视图


279

自动布局使我的生活变得困难。从理论上讲,当我切换时它将非常有用,但是我似乎一直在与之抗争。

我做了一个演示项目,试图寻求帮助。有人知道如何在调整视图大小时使视图之间的间隔均匀地增加或减少吗?

这是三个标签(手动在垂直方向上均匀隔开):

图片1

我想要的是让他们在旋转时均匀地调整其间距(而不是视图尺寸)。默认情况下,顶视图和底视图向中心倾斜:

image2


也许将一个标签的约束条件设置为另一个标签,并将它们的关系设置为“大于或等于”会很有帮助
BergP 2012年

例如以编程方式使用NSLayoutConstraint的这种方法:+(id)constraintWithItem:(id)view1属性:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)与item:(id)view2属性的关系:(NSLayoutAttribute)attr2乘数:(CGFloat)乘数常量:(CGFloat)c
BergP,2012年

我开源了一个通用的UITabBar替代品,该替代品使用Auto Layout概括了这种方法。您可以检查测试以查看如何生成自动布局约束。GGTabBar
Goles,2014年

Answers:


211

因此,我的方法允许您在界面生成器中执行此操作。您要做的是创建“间隔视图”,将其设置为相等地匹配高度。然后将顶部和底部约束添加到标签(请参见屏幕截图)。

在此处输入图片说明

更具体地说,我对“ Spacer View 1”有一个最高约束,以便以低于1000的优先级的高度约束和与所有其他“ spacer view”相等的高度约束进行超级视图。“ Spacer View 4”具有底部空间约束以进行超级视图。每个标签都有与其最接近的“间隔视图”相对应的顶部和底部约束。

注意:请确保您的标签上没有多余的顶部/底部空间限制以进行查看;只是“空间视图”中的那些。这是可以满足的,因为顶部和底部约束分别位于“ Space View 1”和“ Spacer View 4”上。

Duh 1:我复制了我的视图,只是将其置于横向模式,因此您可以看到它起作用。

2:“间隔视图”可能是透明的。

回答3:这种方法可以水平应用。


10
这可行。实际上,间隔视图可以被隐藏,它们仍将产生正确的布局。
paulmelnikow

这超级有帮助。就像只是将间隔视图标记为隐藏的注释一样。然后,您仍然可以在情节提要/ xib中看到它们,但是它们不会显示在您的应用中。非常好。
shulmey

经过数小时的反复尝试,我几乎可以正常工作了。不幸的是,Xcode一直删除我的按钮到顶部和底部的空间约束,打破按钮和分隔符之间的链条,并用无用的顶部到视图的任意约束代替它们。
Desty

在水平布局中,同样的方法适用于我-我有固定宽度的视图,并由等宽间隔视图分隔。不需要将其与任何代码混合在一起真是太好了。谢谢!
Kamil Nomtek.com 2014年

2
无需使用垫片。请参阅下面的答案。
smileBot 2014年

316

瞧,没有间隔!

基于原始答案的评论部分中的建议,尤其是@Rivera的有用建议,我简化了原始答案。

我使用gif来说明这是多么简单。我希望这些gif有用。万一您遇到gif的问题,我在下面以纯屏幕快照提供了旧答案。

说明:

1)添加您的按钮或标签。我正在使用3个按钮。

2)从每个按钮到中心视图添加一个中心x约束:

在此处输入图片说明

3)从每个按钮到底部布局约束添加一个约束:

在此处输入图片说明

4)如下调整上面#3中添加的约束:

a)选择约束, b)删除常数(设置为0), c)如下更改乘数:取按钮数+ 1,从顶部开始,将乘数设置为buttonCountPlus1:1,然后将buttonCountPlus1设置为:2,最后是buttonCountPlus1:3。(如果您有兴趣的话,请在下面的旧答案中说明从何处获得此公式)。

在此处输入图片说明

5)这是一个演示运行!

在此处输入图片说明

注意:如果按钮的高度较大,则由于约束来自按钮的底部,因此您需要使用常数值对此进行补偿。


旧答案


尽管Apple的文档和Erica Sadun的出色著作(《自动版面揭密》)都说过,但是可以均匀地间隔视图而没有间隔。对于要均匀间隔的任意数量的元素,这在IB和代码中都很简单。您所需要的只是一个数学公式,称为“节公式”。这比解释要容易。我将通过在IB中进行演示来尽力而为,但是在代码中这样做同样容易。

在上述示例中,您将

1)首先将每个标签设置为具有中心约束。这很简单。只需控制从每个标签到底部的拖动。

2)按住shift键,因为您可能还要添加我们将要使用的其他约束,即“底部空间到底部布局的指南”。

3)选择“底部到底部的空间布局指南”和“在容器中水平居中”。对所有3个标签执行此操作。

按住shift键为每个标签添加这两个约束

基本上,如果我们希望确定要确定其坐标的标签并将其除以标签总数加1,则可以将一个数字加到IB以获得动态位置。我正在简化公式,但是您可以将其用于设置水平间距或同时设置垂直和水平间距。超级强大!

这是我们的乘数。

标签1 = 1/4 = .25,

标签2 = 2/4 = 0.5,

标签3 = 3/4 = .75

(编辑:@Rivera评论说,您可以直接在乘数字段中直接使用比率,而xCode可以进行数学运算!)

4)因此,让我们选择Label1并选择底部约束。像这样: 在此处输入图片说明

5)在“属性”检查器中选择“第二个项目”。

6)从下拉列表中选择“反转第一和第二项”。

7)将常数和wC hAny值清零。(如果需要,可以在此处添加偏移量)。

8)这是关键部分:在乘数字段中添加第一个乘数0.25。

9)当您在它的顶部时,将顶部的“第一项”设置为“ CenterY”,因为我们想将其居中到标签的y中心。这是所有内容的外观。

在此处输入图片说明

10)对每个标签重复此过程,并插入相关的乘数:Label2为0.5,Label3为0.75。这是所有紧凑型设备所有方向的最终产品!超级简单。我一直在研究许多涉及大量代码和间隔符的解决方案。这是我所见过的最好的解决方案。

更新:@kraftydevil添加了“底部布局指南”仅出现在情节提要中,而不出现在xib中。在xibs中使用“容器的底部空间”。接得好!

在此处输入图片说明


2
我还在努力。我认为您必须使用Xcode 6,因为Xcode 5约束中没有'wC hAny'选项。当我按照您的指示进行操作时,所有子视图都会停留在顶部中心位置。
kraftydevil 2014年

4
@kraftydevil AFAIK底部布局指南仅出现在情节提要中,而不出现在xib中。在xibs中使用“容器的底部空间”。
Timur Kuchkarov 2014年

6
噢-还值得注意的是,IB在Xcode 6个支持比乘法器,所以可以把在诸如像“1:4”, “2:5”, “3:4”,等等
Benjohn

3
试图弄清楚如何水平地执行此操作-像第一个TextView左起1/3,然后第二个TextView 2/3(其余方式)... ...(编辑)得到它-执行前1/3(左一个)尾随空格到右边界,反向,零,像以前一样分配...
kfmfe04 2014年

3
对于水平间距,只需为每个子视图的超级视图设置尾随约束,然后设置比例乘数即可。5:1、5:2、5:3等从左向右移动。
user3344977

98

非常快速的Interface Builder解决方案:

对于要在一个超级视图中均匀分布的任意数量的视图,只需为每个视图设置水平布局的“将中心X对齐到超级视图”约束,或者将每个视图的垂直视图设置为“对齐中心Y超级视图”,然后将“乘数”设置为N:p注意:有些运气更好p:N-见下文

哪里

N = total number of views

p = position of the view including spaces

第一个位置为1,然后为一个空格,使下一个位置为3,因此p成为序列[1,3,5,7,9,...]。适用于任何数量的视图。

因此,如果您有3个空间要看,则看起来像这样:

如何在IB中均匀传播视图的图示

编辑注意:N:pp:N取决于对齐约束的关系顺序。如果“第一项”是Superview.Center,则可以使用p:N;如果“ Superview.Center”是“第二项”,则可以使用N:p。如有疑问,请尝试一下... :-)


如果您切换到其他语言,该解决方案将无法反映设计
wod 2015年

@wod是什么意思?切换到另一种语言?到底有什么坏处?
2015年

4
如果元素的大小不一样怎么办?这种模式不能正常工作,对吗?
ucangetit

2
我不知道这个解决方案是正确的吗?屏幕边缘与外部元件之间的间隙是否不同于外部元件与中心元件之间的间隙?
迈尔斯2015年

6
对于我来说,这不是“原样”,我不得不逆转乘数(例如,第一项的3:1转换为1:3)。如果有帮助,就把它扔在那里。
Ces

70

从iOS 9开始,Apple借助(期待已久)简化了操作UIStackView。只需选择要包含在Interface Builder中的视图,然后选择Editor-> Embed In-> Stack视图。为堆栈视图设置适当的宽度/高度/边距约束,并确保将“分布”属性设置为“等间距”:

当然,如果您需要支持iOS 8或更低版本,则必须选择其他选项之一。


是的,很遗憾,它不具有复古兼容性,因此,直到您的应用需要对IOS8 @Mete响应的支持,这才是目前为止最好的。
ZiggyST '16

51

我一直在喜欢自动布局并讨厌它。爱它的关键似乎在于接受以下几点:

  1. 界面构建器的编辑和约束的“有用”自动创建对几乎最琐碎的情况几乎没有用。
  2. 创建类别以简化常见操作可节省生命,因为代码是如此重复且冗长。

也就是说,您尝试的操作并不简单,并且在界面生成器中将很难实现。用代码做起来很简单。此代码中的中viewDidLoad,创建和定位三个标签的方式是:

// Create three labels, turning off the default constraints applied to views created in code
UILabel *label1 = [UILabel new];
label1.translatesAutoresizingMaskIntoConstraints = NO;
label1.text = @"Label 1";

UILabel *label2 = [UILabel new];
label2.translatesAutoresizingMaskIntoConstraints = NO;
label2.text = @"Label 2";

UILabel *label3 = [UILabel new];
label3.translatesAutoresizingMaskIntoConstraints = NO;
label3.text = @"Label 3";

// Add them all to the view
[self.view addSubview:label1];
[self.view addSubview:label2];
[self.view addSubview:label3];

// Center them all horizontally
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label1 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];

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

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

// Center the middle one vertically
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label2 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]];

// Position the top one half way up
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label1 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:label2 attribute:NSLayoutAttributeCenterY multiplier:0.5 constant:0]];

// Position the bottom one half way down
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label3 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:label2 attribute:NSLayoutAttributeCenterY multiplier:1.5 constant:0]];

就像我说的那样,该代码通过中的几个类别方法被大大简化了UIView,但是为了清楚起见,我在这里做了很长的路要走。

该类别在这里供有兴趣的人士使用,它具有一种沿特定轴均匀间隔排列视图阵列的方法。


先生,您是救生员。直到现在我还不了解#1。界面生成器使我发疯,但我认为它能够完成所有相同的事情。无论如何,我还是喜欢使用代码,所以这是完美的,并且我会正确地使用类别方法。
nothappybob 2012年

虽然绝对接近,但这并不能完全解决所要求的确切问题(将视图大小保持相同,并且子视图之间的间距均匀)。该解决方案是精确的一般情况答案。
smileyborg

21

这些解决方案中的大多数都取决于项目数量的奇数,以便您可以取中间的项目并居中。如果您有偶数个项目仍要平均分配怎么办?这是一个更通用的解决方案。此类别将沿垂直轴或水平轴均匀分布任何数量的项目。

在其超级视图内垂直分布4个标签的用法示例:

[self.view addConstraints:
     [NSLayoutConstraint constraintsForEvenDistributionOfItems:@[label1, label2, label3, label4]
                                        relativeToCenterOfItem:self.view
                                                    vertically:YES]];

NSLayoutConstraint + EvenDistribution.h

@interface NSLayoutConstraint (EvenDistribution)

/**
 * Returns constraints that will cause a set of views to be evenly distributed horizontally
 * or vertically relative to the center of another item. This is used to maintain an even
 * distribution of subviews even when the superview is resized.
 */
+ (NSArray *) constraintsForEvenDistributionOfItems:(NSArray *)views
                             relativeToCenterOfItem:(id)toView
                                         vertically:(BOOL)vertically;

@end

NSLayoutConstraint + EvenDistribution.m

@implementation NSLayoutConstraint (EvenDistribution)

+(NSArray *)constraintsForEvenDistributionOfItems:(NSArray *)views
                           relativeToCenterOfItem:(id)toView vertically:(BOOL)vertically
{
    NSMutableArray *constraints = [NSMutableArray new];
    NSLayoutAttribute attr = vertically ? NSLayoutAttributeCenterY : NSLayoutAttributeCenterX;

    for (NSUInteger i = 0; i < [views count]; i++) {
        id view = views[i];
        CGFloat multiplier = (2*i + 2) / (CGFloat)([views count] + 1);
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view
                                                                      attribute:attr
                                                                      relatedBy:NSLayoutRelationEqual
                                                                         toItem:toView
                                                                      attribute:attr
                                                                     multiplier:multiplier
                                                                       constant:0];
        [constraints addObject:constraint];
    }

    return constraints;
}

@end

1
它的工作方式是删除所有约束,然后立即将Width和Height约束添加到这些对象,然后调用方法ConstraintsForEvenDistributionOfItems:relativeToCenterOfItem:vertical:
carlos_ms 2013年

@carlos_ms我很想看看您的代码,因为当我尝试偶数个项目时,此代码在切换设备方向时非常有效。您确定没有无意间导致冲突的其他约束(例如自动调整大小约束)吗?
Ben Dolman

这是创建4个标签并沿垂直轴均匀隔开的示例:gist.github.com/bdolman/5379465
Ben Dolman

20

正确和最简单的方法是使用堆栈视图。

  1. 将标签/视图添加到堆栈视图

在此处输入图片说明

  1. 选择“堆栈视图”并将“ 分布”设置为“ 等间距”

在此处输入图片说明

  1. 将“ 间距到最近邻居”约束添加堆栈视图并更新框架:

在此处输入图片说明

  1. 向所有标签添加高度限制(可选)。仅对于没有内在大小的视图才需要)。例如,标签在此不需要高度限制,例如仅需要将numberOfLines设置为3或0。

在此处输入图片说明

  1. 享受预览:

在此处输入图片说明


2
这仅是> = iOS 9.0,因此在开始实施之前请检查您的部署目标:)很好的是,它已经添加了!
DivideByZer0

1
2018年至今,使用堆栈视图
Jingshao Chen

17

查看开源库PureLayout。它提供了一些用于分发视图的API方法,包括以下变量:每个视图之间的间距是固定的(视图大小根据需要而变化),每个视图的尺寸是固定的(视图之间的间距根据需要而变化)。注意,所有这些都是在使用任何“间隔符视图”的情况下完成的。

NSArray + PureLayout.h

// NSArray+PureLayout.h

// ...

/** Distributes the views in this array equally along the selected axis in their superview. Views will be the same size (variable) in the dimension along the axis and will have spacing (fixed) between them. */
- (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
                                alignedTo:(ALAttribute)alignment
                         withFixedSpacing:(CGFloat)spacing;

/** Distributes the views in this array equally along the selected axis in their superview. Views will be the same size (fixed) in the dimension along the axis and will have spacing (variable) between them. */
- (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
                                alignedTo:(ALAttribute)alignment
                            withFixedSize:(CGFloat)size;

// ...

由于它都是开源的,因此如果您有兴趣了解如何在没有隔离视图的情况下实现此目标,请看一下实现。(这取决于同时利用constant multiplier约束。)


干得好!虽然您可能应该重命名该存储库,以便可以通过cocoapods将其使用。
jrturton

9

我有一个类似的问题,并发现了这篇文章。但是,当前提供的答案都无法以您想要的方式解决问题。它们不会平均分配间距,而是平均分配标签的中心。重要的是要了解这是不一样的。我已经构建了一个小图来说明这一点。

视图间距图

有3个意见,都是20pt高。使用任何建议的方法均等地分布视图的中心,并为您提供插图的布局。请注意,视图的y中心间距相等。但是,超级视图和顶视图之间的间隔为15pt,而子视图之间的间隔仅为5pt。为了使视图均等,它们都应为10pt,即所有蓝色箭头均应为10pt。

不过,我还没有想出一个好的通用解决方案。目前,我最好的想法是在子视图之间插入“间距视图”,并将间距视图的高度设置为相等。


1
我使用了隐藏间隔视图的解决方案,效果很好。
paulmelnikow

8

我能够在IB中完全解决此问题:

  1. 进行约束,以使每个子视图的中心Y与超级视图的底部边缘对齐。
  2. 将每个约束的乘数设置为1 / 2n,3 / 2n,5 / 2n,…,n-1 / 2n,其中n是您要分配的子视图数。

因此,如果您有三个标签,请将每个约束的乘数设置为0.1666667、0.5、0.833333。


1
我实际上不得不做1 / n 3 / n 5 / n 7 / n直到(2n-1)/ n
Hamzah Malik

5

我找到了一种完美而简单的方法。自动布局不允许您平均调整空间大小,但可以让您平均调整视图大小。只需在您的字段之间放置一些不可见的视图,并告诉自动布局以使它们保持相同大小即可。它完美地工作!

初始XIB

拉伸XIB

值得注意的是;当我在界面设计器中减小尺寸时,有时会感到困惑,并在原来的位置留下标签,如果将尺寸更改为奇数,则会发生冲突。否则,它会完美运行。

编辑:我发现冲突成为一个问题。因此,我选择了一个间距约束,将其删除并用两个约束替换,即大于或等于和小于或等于。两者的大小相同,并且优先级比其他限制要低得多。结果没有进一步的冲突。


4

在Ben Dolman的答案的基础上,这可以更均匀地分布视图(使用填充等):

+(NSArray *)constraintsForEvenDistributionOfItems:(NSArray *)views
                           relativeToCenterOfItem:(id)toView vertically:(BOOL)vertically
{
    NSMutableArray *constraints = [NSMutableArray new];
    NSLayoutAttribute attr = vertically ? NSLayoutAttributeCenterY : NSLayoutAttributeCenterX;

    CGFloat min = 0.25;
    CGFloat max = 1.75;
    CGFloat d = (max-min) / ([views count] - 1);
    for (NSUInteger i = 0; i < [views count]; i++) {
        id view = views[i];
        CGFloat multiplier = i * d + min;
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view
                                                                      attribute:attr
                                                                      relatedBy:NSLayoutRelationEqual
                                                                         toItem:toView
                                                                      attribute:attr
                                                                     multiplier:multiplier
                                                                       constant:0];
        [constraints addObject:constraint];
    }

    return constraints;
}


3

使用标签至少可以正常工作:

@"H:|-15-[first(==second)]-[second(==third)]-[third(==first)]-15-|

如果第一个的宽度与第二个的宽度相同,第二个的宽度与第三个的宽度相同,那么它们的宽度都将相同。


3

迅捷3版本

let _redView = UIView()
        _redView.backgroundColor = UIColor.red
        _redView.translatesAutoresizingMaskIntoConstraints = false

        let _yellowView = UIView()
        _yellowView.backgroundColor = UIColor.yellow
        _yellowView.translatesAutoresizingMaskIntoConstraints = false

        let _blueView = UIView()
        _blueView.backgroundColor = UIColor.blue
        _blueView.translatesAutoresizingMaskIntoConstraints = false

        self.view.addSubview(_redView)
        self.view.addSubview(_yellowView)
        self.view.addSubview(_blueView)

        var views = ["_redView": _redView, "_yellowView": _yellowView, "_blueView":_blueView]

        var nslayoutConstraint_H = NSLayoutConstraint.constraints(withVisualFormat: "|->=0-[_redView(40)]->=0-[_yellowView(40)]->=0-[_blueView(40)]->=0-|", options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views)
        self.view.addConstraints(nslayoutConstraint_H)

        var nslayoutConstraint_V = NSLayoutConstraint.constraints(withVisualFormat: "V:[_redView(60)]", options: NSLayoutFormatOptions.init(rawValue: 0), metrics: nil, views: views)
        self.view.addConstraints(nslayoutConstraint_V)


        let constraint_red = NSLayoutConstraint.init(item: self.view, attribute: .centerY, relatedBy: .equal, toItem: _redView, attribute: .centerY, multiplier: 1, constant: 0)
        self.view.addConstraint(constraint_red)

        let constraint_yellow = NSLayoutConstraint.init(item: self.view, attribute: .centerX, relatedBy: .equal, toItem: _yellowView, attribute: .centerX, multiplier: 1, constant: 0)
        self.view.addConstraint(constraint_yellow)

        let constraint_yellow1 = NSLayoutConstraint.init(item: _redView, attribute: .centerX, relatedBy: .equal, toItem: _yellowView, attribute: .leading, multiplier: 0.5, constant: 0)
        self.view.addConstraint(constraint_yellow1)

        let constraint_yellow2 = NSLayoutConstraint.init(item: _blueView, attribute: .centerX, relatedBy: .equal, toItem: _yellowView, attribute: .leading, multiplier: 1.5, constant: 40)
        self.view.addConstraint(constraint_yellow2)

1
详细说明此代码如何回答问题将对将来的访问者有所帮助。
2016年

2

我知道距第一个答案已经有一段时间了,但是我遇到了同样的问题,我想分享自己的解决方案。子孙后代...

我在viewDidLoad上设置视图:

- (void)viewDidLoad {

    [super viewDidLoad];

    cancelButton = [UIButton buttonWithType: UIButtonTypeRoundedRect];
    cancelButton.translatesAutoresizingMaskIntoConstraints = NO;
    [cancelButton setTitle:@"Cancel" forState:UIControlStateNormal];
    [self.view addSubview:cancelButton];

    middleButton = [UIButton buttonWithType: UIButtonTypeRoundedRect];
    middleButton.translatesAutoresizingMaskIntoConstraints = NO;
    [middleButton setTitle:@"Middle" forState:UIControlStateNormal];
    [self.view addSubview:middleButton];

    nextButton = [UIButton buttonWithType: UIButtonTypeRoundedRect];
    nextButton.translatesAutoresizingMaskIntoConstraints = NO;
    [nextButton setTitle:@"Next" forState:UIControlStateNormal];
    [self.view addSubview:nextButton];


    [self.view setNeedsUpdateConstraints];

}

然后,在updateViewConstrains上,首先删除所有约束,然后创建视图字典,然后计算视图之间要使用的空间。之后,我仅使用可视语言格式来设置约束:

- (void)updateViewConstraints {


    [super updateViewConstraints];

    [self.view removeConstraints:self.view.constraints];

    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(cancelButton, nextButton, middleButton);

    float distance=(self.view.bounds.size.width-cancelButton.intrinsicContentSize.width-nextButton.intrinsicContentSize.width-middleButton.intrinsicContentSize.width-20-20)/  ([viewsDictionary count]-1);  // 2 times 20 counts for the left & rigth margins
    NSNumber *distancies=[NSNumber numberWithFloat:distance];

//    NSLog(@"Distancies: %@", distancies);
//    
//    NSLog(@"View Width: %f", self.view.bounds.size.width);
//    NSLog(@"Cancel Width: %f", cancelButton.intrinsicContentSize.width);
//    NSLog(@"Middle Width: %f", middleButton.intrinsicContentSize.width);
//    NSLog(@"Next Width: %f", nextButton.intrinsicContentSize.width);



    NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-[cancelButton]-dis-[middleButton]-dis-[nextButton]-|"
                                                                   options:NSLayoutFormatAlignAllBaseline
                                                                   metrics:@{@"dis":distancies}
                                                                     views:viewsDictionary];


    [self.view addConstraints:constraints];



    constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[nextButton]-|"
                                                          options:0
                                                          metrics:nil
                                                            views:viewsDictionary];
    [self.view addConstraints:constraints];



}

这种方法的好处是您不必做任何数学运算。我并不是说这是完美的解决方案,但我致力于的是我试图实现的布局。

希望对您有所帮助。


2

这是一个解决方案,即使子视图具有唯一的大小,也可以将任何数量的子视图垂直居中。您想要做的是制作一个中级容器,将其置于父视图的中心,然后将所有子视图放入容器中,并使它们彼此相对。但至关重要的是,您还需要将它们限制在容器的顶部底部,以便可以正确调整容器的大小并在超级视图中居中。通过从其子视图确定正确的高度,可以将容器垂直居中。

在此示例中,self是您将所有子视图居中的超级视图。

NSArray *subviews = @[ (your subviews in top-to-bottom order) ];

UIView *container = [[UIView alloc] initWithFrame:CGRectZero];
container.translatesAutoresizingMaskIntoConstraints = NO;
for (UIView *subview in subviews) {
    subview.translatesAutoresizingMaskIntoConstraints = NO;
    [container addSubview:subview];
}
[self addSubview:container];

[self addConstraint:[NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual
                                                    toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0f constant:0.0f]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual
                                                    toItem:self attribute:NSLayoutAttributeRight multiplier:1.0f constant:0.0f]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual
                                                    toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0.0f]];

if (0 < subviews.count) {
    UIView *firstSubview = subviews[0];
    [container addConstraint:[NSLayoutConstraint constraintWithItem:firstSubview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
                                                             toItem:container attribute:NSLayoutAttributeTop multiplier:1.0f constant:0.0f]];
    UIView *lastSubview = subviews.lastObject;
    [container addConstraint:[NSLayoutConstraint constraintWithItem:lastSubview attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual
                                                             toItem:container attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0.0f]];

    UIView *subviewAbove = nil;
    for (UIView *subview in subviews) {
        [container addConstraint:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual
                                                                 toItem:container attribute:NSLayoutAttributeCenterX multiplier:1.0f constant:0.0f]];
        if (subviewAbove) {
            [container addConstraint:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
                                                                     toItem:subviewAbove attribute:NSLayoutAttributeBottom multiplier:1.0f constant:10.0f]];
        }
        subviewAbove = subview;
    }
}

当使用必须居中的不同大小的视图时,这是最佳解决方案。
noobsmcgoobs

2

我只是使用乘法器功能解决了我的问题。我不确定它是否适用于所有情况,但对我来说效果很好。我正在使用Xcode 6.3 FYI。

我最终要做的是:

1)首先将按钮放置在320像素宽的屏幕上,按照我希望它在320像素设备上的显示方式进行分配。

步骤1:放置按钮

2)然后,我在所有按钮上添加了领先的Space约束以进行超级视图。

步骤2:添加领先的空间限制

3)然后,我修改了前导空间的属性,以使常数为0,乘数为x偏移除以屏幕的宽度(例如,我的第一个按钮距左边缘为8px,因此将乘数设置为8/320)

4)然后,这里的重要步骤是将约束关系中的第二个项目更改为superview.Trailing而不是superview.leading。这是关键,因为Superview.Leading为0且在我的情况下为320,所以在320px的设备上8/320为8 px,然后当Superview的宽度更改为640或其他值时,所有视图均以相对于宽度的比率移动320像素的屏幕尺寸。这里的数学更容易理解。

步骤3和4:将乘数更改为xPos / screenWidth并将第二项设置为.Trailing


2

许多答案是不正确的,但是却有很多计数。在这里,我只是以编程方式编写一个解决方案,这三个视图是水平对齐的,没有使用间隔视图,但是仅在情节提要中使用标签的宽度已知时才起作用

NSDictionary *views = NSDictionaryOfVariableBindings(_redView, _yellowView, _blueView);

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|->=0-[_redView(40)]->=0-[_yellowView(40)]->=0-[_blueView(40)]->=0-|" options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom metrics:nil views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_redView(60)]" options:0 metrics:nil views:views]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:_redView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_yellowView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:_redView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_yellowView attribute:NSLayoutAttributeLeading multiplier:0.5 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:_blueView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_yellowView attribute:NSLayoutAttributeLeading multiplier:1.5 constant:40]];

2

这是另一个答案。我在回答类似的问题,并且看到指向该问题的链接。我没有看到任何类似于我的答案。所以,我想到了在这里写。

  class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.whiteColor()
        setupViews()
    }

    var constraints: [NSLayoutConstraint] = []

    func setupViews() {

        let container1 = createButtonContainer(withButtonTitle: "Button 1")
        let container2 = createButtonContainer(withButtonTitle: "Button 2")
        let container3 = createButtonContainer(withButtonTitle: "Button 3")
        let container4 = createButtonContainer(withButtonTitle: "Button 4")

        view.addSubview(container1)
        view.addSubview(container2)
        view.addSubview(container3)
        view.addSubview(container4)

        [

            // left right alignment
            container1.leftAnchor.constraintEqualToAnchor(view.leftAnchor, constant: 20),
            container1.rightAnchor.constraintEqualToAnchor(view.rightAnchor, constant: -20),
            container2.leftAnchor.constraintEqualToAnchor(container1.leftAnchor),
            container2.rightAnchor.constraintEqualToAnchor(container1.rightAnchor),
            container3.leftAnchor.constraintEqualToAnchor(container1.leftAnchor),
            container3.rightAnchor.constraintEqualToAnchor(container1.rightAnchor),
            container4.leftAnchor.constraintEqualToAnchor(container1.leftAnchor),
            container4.rightAnchor.constraintEqualToAnchor(container1.rightAnchor),


            // place containers one after another vertically
            container1.topAnchor.constraintEqualToAnchor(view.topAnchor),
            container2.topAnchor.constraintEqualToAnchor(container1.bottomAnchor),
            container3.topAnchor.constraintEqualToAnchor(container2.bottomAnchor),
            container4.topAnchor.constraintEqualToAnchor(container3.bottomAnchor),
            container4.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor),


            // container height constraints
            container2.heightAnchor.constraintEqualToAnchor(container1.heightAnchor),
            container3.heightAnchor.constraintEqualToAnchor(container1.heightAnchor),
            container4.heightAnchor.constraintEqualToAnchor(container1.heightAnchor)
            ]
            .forEach { $0.active = true }
    }


    func createButtonContainer(withButtonTitle title: String) -> UIView {
        let view = UIView(frame: .zero)
        view.translatesAutoresizingMaskIntoConstraints = false

        let button = UIButton(type: .System)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle(title, forState: .Normal)
        view.addSubview(button)

        [button.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor),
            button.leftAnchor.constraintEqualToAnchor(view.leftAnchor),
            button.rightAnchor.constraintEqualToAnchor(view.rightAnchor)].forEach { $0.active = true }

        return view
    }
}

在此处输入图片说明

同样,使用iOS9 UIStackViews也可以很容易地做到这一点。

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.greenColor()
        setupViews()
    }

    var constraints: [NSLayoutConstraint] = []

    func setupViews() {

        let container1 = createButtonContainer(withButtonTitle: "Button 1")
        let container2 = createButtonContainer(withButtonTitle: "Button 2")
        let container3 = createButtonContainer(withButtonTitle: "Button 3")
        let container4 = createButtonContainer(withButtonTitle: "Button 4")

        let stackView = UIStackView(arrangedSubviews: [container1, container2, container3, container4])
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.axis = .Vertical
        stackView.distribution = .FillEqually
        view.addSubview(stackView)

        [stackView.topAnchor.constraintEqualToAnchor(view.topAnchor),
            stackView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor),
            stackView.leftAnchor.constraintEqualToAnchor(view.leftAnchor, constant: 20),
            stackView.rightAnchor.constraintEqualToAnchor(view.rightAnchor, constant: -20)].forEach { $0.active = true }
    }


    func createButtonContainer(withButtonTitle title: String) -> UIView {
        let button = UIButton(type: .Custom)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.backgroundColor = UIColor.redColor()
        button.setTitleColor(UIColor.whiteColor(), forState: .Normal)
        button.setTitle(title, forState: .Normal)
        let buttonContainer = UIStackView(arrangedSubviews: [button])
        buttonContainer.distribution = .EqualCentering
        buttonContainer.alignment = .Center
        buttonContainer.translatesAutoresizingMaskIntoConstraints = false
        return buttonContainer
    }
}

请注意,它与上述方法完全相同。它添加了四个均等填充的容器视图,并且将一个视图添加到了每个居中对齐的堆栈视图。但是,此版本的UIStackView减少了一些代码,看起来不错。


1

另一种方法可能是使顶部和底部标签分别具有相对于视图顶部和底部的约束,并使中间视图具有分别相对于第一视图和第三视图的顶部和底部约束。

请注意,对约束的控制要比通过拖动视图彼此靠近直到出现虚线指导要多得多,这些约束指示将要形成的两个对象之间的约束,而不是对象和超级视图之间的约束。

在这种情况下,您可能希望将约束更改为“大于或等于”所需值,而不是“等于”以允许它们调整大小。不知道这是否能满足您的要求。


1

在InterfaceBuilder中解决此问题的简单方法:

将居中标签(label2)设置为“容器中的水平中心”和“容器中的垂直中心”

选择居中标签和顶部标签(label1 + label2),并为“垂直间距” 添加两个约束。一个大于或等于最小间距。其中小于或等于最大间距。

居中标签和底部标签(label2 + label3)相同。

另外,您还可以向label1添加两个约束-SuperView的顶部空间,向label2向SuperView底部空间的两个约束。

结果将是所有4个间距将均等地更改其大小。


2
我发现这仅在您调整超级视图的大小时有效,以便间隔恰好采用最小值或最大值。当您将其大小调整为介于两者之间的值时,子视图不会平均分布。
多里安·罗伊

1

我做了一个可能会有所帮助的功能。此用法示例:

 [self.view addConstraints: [NSLayoutConstraint fluidConstraintWithItems:NSDictionaryOfVariableBindings(button1, button2, button3)
                                                                asString:@[@"button1", @"button2", @"button3"]
                                                               alignAxis:@"V"
                                                          verticalMargin:100
                                                        horizontalMargin:50
                                                             innerMargin:25]];

会导致垂直分布(对不起,没有10个声誉可嵌入图像)。如果您更改轴和一些边距值:

alignAxis:@"H"
verticalMargin:120
horizontalMargin:20
innerMargin:10

您将获得该水平分布

我是iOS的新手,但是瞧!

EvenDistribution.h

@interface NSLayoutConstraint (EvenDistribution)

/**
 * Returns constraints that will cause a set of subviews
 * to be evenly distributed along an axis.
 */
+ (NSArray *)  fluidConstraintWithItems:(NSDictionary *) views
                               asString:(NSArray *) stringViews
                              alignAxis:(NSString *) axis
                         verticalMargin:(NSUInteger) vMargin
                       horizontalMargin:(NSUInteger) hMargin
                            innerMargin:(NSUInteger) inner;
@end

EvenDistribution.m

#import "EvenDistribution.h"

@implementation NSLayoutConstraint (EvenDistribution)

+ (NSArray *) fluidConstraintWithItems:(NSDictionary *) dictViews
                              asString:(NSArray *) stringViews
                             alignAxis:(NSString *) axis
                        verticalMargin:(NSUInteger) vMargin
                      horizontalMargin:(NSUInteger) hMargin
                           innerMargin:(NSUInteger) iMargin

{
    NSMutableArray *constraints = [NSMutableArray arrayWithCapacity: dictViews.count];
    NSMutableString *globalFormat = [NSMutableString stringWithFormat:@"%@:|-%d-",
                                     axis,
                                     [axis isEqualToString:@"V"] ? vMargin : hMargin
                                     ];



        for (NSUInteger i = 0; i < dictViews.count; i++) {

            if (i == 0)
                [globalFormat appendString:[NSString stringWithFormat: @"[%@]-%d-", stringViews[i], iMargin]];
            else if(i == dictViews.count - 1)
                [globalFormat appendString:[NSString stringWithFormat: @"[%@(==%@)]-", stringViews[i], stringViews[i-1]]];
            else
               [globalFormat appendString:[NSString stringWithFormat: @"[%@(==%@)]-%d-", stringViews[i], stringViews[i-1], iMargin]];

            NSString *localFormat = [NSString stringWithFormat: @"%@:|-%d-[%@]-%d-|",
                                     [axis isEqualToString:@"V"] ? @"H" : @"V",
                                     [axis isEqualToString:@"V"] ? hMargin : vMargin,
                                     stringViews[i],
                                     [axis isEqualToString:@"V"] ? hMargin : vMargin];

            [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:localFormat
                                                                                     options:0
                                                                                     metrics:nil
                                                                                       views:dictViews]];


    }
    [globalFormat appendString:[NSString stringWithFormat:@"%d-|",
                                [axis isEqualToString:@"V"] ? vMargin : hMargin
                                ]];

    [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:globalFormat
                                                                             options:0
                                                                             metrics:nil
                                                                               views:dictViews]];

    return constraints;

}

@end

1

是的,您可以仅在界面生成器中执行此操作,而无需编写代码-一个警告是,您正在调整标签的大小,而不是分配空白。在这种情况下,将标签2的X和Y对齐到父视图,以便将其固定在中心。然后将标签1的垂直空间设置为超级视图,并将标签2设置为标准,重复标签3。设置标签2后,设置标签1和3的最简单方法是调整它们的大小,直到它们对齐为止。

这是水平显示,请注意标签1和2之间的垂直间距设置为标准:水平显示

这是肖像版本:在此处输入图片说明

我意识到,由于标签之间的标准空间与Superview的标准空间之间的差异,它们在基线之间并不是绝对等距的100%。如果您不满意,请将大小设置为0,而不是标准大小


1

我仅为第一个项目设置宽度值(> =宽度),并为每个项目之间设置最小距离(> =距离)。然后,我使用Ctrl拖动第一个,第二个,第三个...项目,以在各项目之间建立依赖关系。

在此处输入图片说明


1

聚会晚了,但是我有一个有效的解决方案,可以水平间隔创建菜单。使用==NSLayoutConstraint 可以轻松完成

const float MENU_HEIGHT = 40;

- (UIView*) createMenuWithLabels: (NSArray *) labels
    // labels is NSArray of NSString
    UIView * backgroundView = [[UIView alloc]init];
    backgroundView.translatesAutoresizingMaskIntoConstraints = false;

    NSMutableDictionary * views = [[NSMutableDictionary alloc] init];
    NSMutableString * format = [[NSMutableString alloc] initWithString: @"H:|"];
    NSString * firstLabelKey;

    for(NSString * str in labels)
    {
        UILabel * label = [[UILabel alloc] init];
        label.translatesAutoresizingMaskIntoConstraints = false;
        label.text = str;
        label.textAlignment = NSTextAlignmentCenter;
        label.textColor = [UIColor whiteColor];
        [backgroundView addSubview: label];
        [label fixHeightToTopBounds: MENU_HEIGHT-2];
        [backgroundView addConstraints: [label fixHeightToTopBounds: MENU_HEIGHT]];
        NSString * key = [self camelCaseFromString: str];
        [views setObject: label forKey: key];
        if(firstLabelKey == nil)
        {
            [format appendString: [NSString stringWithFormat: @"[%@]", key]];
            firstLabelKey = key;
        }
        else
        {
            [format appendString: [NSString stringWithFormat: @"[%@(==%@)]", key, firstLabelKey]];
        }
    }

    [format appendString: @"|"];

    NSArray * constraints = [NSLayoutConstraint constraintsWithVisualFormat: (NSString *) format
                                                                               options: 0
                                                                               metrics: nil
                                                                                 views: (NSDictionary *) views];
    [backgroundView addConstraints: constraints];
    return backgroundView;
}

0

Android在我想模仿的基于约束的布局系统中将视图链接在一起的方法。搜索将我带到这里,但没有一个答案奏效。我不想使用StackViews,因为它们会使我痛苦不堪,而不是预先保存。我最终创建了一个使用位于视图之间的UILayoutGuides的解决方案。通过控制宽度,可以使用Android的说法来分配不同类型的分布和链样式。该函数接受前导和尾随锚,而不是父视图。这允许将链放置在两个任意视图之间,而不是分布在父视图内部。它确实使用UILayoutGuide仅在iOS 9+中可用的功能,但这不再是问题。

public enum LayoutConstraintChainStyle {
    case spread //Evenly distribute between the anchors
    case spreadInside //Pin the first & last views to the sides and then evenly distribute
    case packed //The views have a set space but are centered between the anchors.
}

public extension NSLayoutConstraint {

    static func chainHorizontally(views: [UIView],
                                  leadingAnchor: NSLayoutXAxisAnchor,
                                  trailingAnchor: NSLayoutXAxisAnchor,
                                  spacing: CGFloat = 0.0,
                                  style: LayoutConstraintChainStyle = .spread) -> [NSLayoutConstraint] {
    var constraints = [NSLayoutConstraint]()
    guard views.count > 1 else { return constraints }
    guard let first = views.first, let last = views.last, let superview = first.superview else { return constraints }

    //Setup the chain of views
    var distributionGuides = [UILayoutGuide]()
    var previous = first
    let firstGuide = UILayoutGuide()
    superview.addLayoutGuide(firstGuide)
    distributionGuides.append(firstGuide)
    firstGuide.identifier = "ChainDistribution\(distributionGuides.count)"
    constraints.append(firstGuide.leadingAnchor.constraint(equalTo: leadingAnchor))
    constraints.append(first.leadingAnchor.constraint(equalTo: firstGuide.trailingAnchor, constant: spacing))
    views.dropFirst().forEach { view in
        let g = UILayoutGuide()
        superview.addLayoutGuide(g)
        distributionGuides.append(g)
        g.identifier = "ChainDistribution\(distributionGuides.count)"
        constraints.append(contentsOf: [
            g.leadingAnchor.constraint(equalTo: previous.trailingAnchor),
            view.leadingAnchor.constraint(equalTo: g.trailingAnchor)
        ])
        previous = view
    }
    let lastGuide = UILayoutGuide()
    superview.addLayoutGuide(lastGuide)
    constraints.append(contentsOf: [lastGuide.leadingAnchor.constraint(equalTo: last.trailingAnchor),
                                    lastGuide.trailingAnchor.constraint(equalTo: trailingAnchor)])
    distributionGuides.append(lastGuide)

    //Space the according to the style.
    switch style {
    case .packed:
        if let first = distributionGuides.first, let last = distributionGuides.last {
            constraints.append(first.widthAnchor.constraint(greaterThanOrEqualToConstant: spacing))
            constraints.append(last.widthAnchor.constraint(greaterThanOrEqualToConstant: spacing))
            constraints.append(last.widthAnchor.constraint(equalTo: first.widthAnchor))
            constraints.append(contentsOf:
                distributionGuides.dropFirst().dropLast()
                    .map { $0.widthAnchor.constraint(equalToConstant: spacing) }
                )
        }
    case .spread:
        if let first = distributionGuides.first {
            constraints.append(contentsOf:
                distributionGuides.dropFirst().map { $0.widthAnchor.constraint(equalTo: first.widthAnchor) })
        }
    case .spreadInside:
        if let first = distributionGuides.first, let last = distributionGuides.last {
            constraints.append(first.widthAnchor.constraint(equalToConstant: spacing))
            constraints.append(last.widthAnchor.constraint(equalToConstant: spacing))
            let innerGuides = distributionGuides.dropFirst().dropLast()
            if let key = innerGuides.first {
                constraints.append(contentsOf:
                    innerGuides.dropFirst().map { $0.widthAnchor.constraint(equalTo: key.widthAnchor) }
                )
            }
        }
    }

    return constraints
}

0

我想水平对齐5张图像,所以最终我跟随Mete的回应有一点点差异。

第一张图片将在等于0且乘数为1:5的容器中水平居中:

第一张图片

第二个图像将在等于0且乘数为3:5的容器中水平居中: 第二张图片

其余的图像都一样。例如,第五张(也是最后一张)图像将在等于0且乘数为9:5的容器中水平居中: 最后一张图片

正如Mete所解释的,顺序为1、3、5、7、9等。这些位置遵循相同的逻辑:第一个位置为1,然后为空格,第二个位置为3,依此类推。


-1

您为什么不创建tableView并进行 isScrollEnabled = false


不了解投票否决权,我的解决方案是在框外思考。当可可在UITableView中为您提供列表时,为什么要烦恼使用自动布局创建列表呢?
乔什·奥康纳
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.