自动布局使我的生活变得困难。理论上,当我切换时它会非常有用,但我似乎一直在与它作斗争。
我做了一个演示项目来尝试寻求帮助。有谁知道如何在调整视图大小时使视图之间的空间均匀增加或减少?
这是三个标签(手动垂直均匀间隔):
https://i.stack.imgur.com/rc7LK.png
我想要的是让他们在我旋转时均匀地调整间距(而不是视图大小)。默认情况下,顶部和底部视图向中心挤压:
https://i.stack.imgur.com/APdX9.png
看,没有间隔!
根据我原始答案的评论部分中的建议,尤其是@Rivera 的有用建议,我简化了我的原始答案。
我正在使用 gif 来说明这是多么简单。我希望你觉得这些 GIF 很有帮助。以防万一您对 gif 文件有疑问,我在下面的旧答案中包含了纯屏幕截图。
指示:
1) 添加您的按钮或标签。我正在使用 3 个按钮。
2)将每个按钮的中心x约束添加到超级视图:
https://i.stack.imgur.com/s4utU.gif
3)从每个按钮添加一个约束到底部布局约束:
https://i.stack.imgur.com/NrSmj.gif
4) 调整上面#3中添加的约束如下:
a) 选择约束,b) 移除常量(设置为 0),c) 改变乘数如下:取按钮数 + 1,从顶部开始,设置乘数为 buttonCountPlus1:1,然后是 buttonCountPlus1 :2,最后是 buttonCountPlus1:3。 (如果您有兴趣,我会在下面的旧答案中解释我从哪里得到这个公式)。
https://i.stack.imgur.com/BIIi9.gif
5)这是一个正在运行的演示!
https://i.stack.imgur.com/6AfaH.gif
注意:如果您的按钮具有较大的高度,那么您将需要在常量值中对此进行补偿,因为约束来自按钮的底部。
旧答案
尽管 Apple 的文档和 Erica Sadun 的优秀书籍(Auto Layout Demystified)说了什么,但可以在没有间隔的情况下均匀地分隔视图。这在 IB 和代码中非常简单,可以用于您希望均匀分布的任意数量的元素。您所需要的只是一个称为“截面公式”的数学公式。做起来比解释要简单。我会尽力在 IB 中演示它,但在代码中也很容易做到。
在有问题的示例中,您将
1)首先将每个标签设置为具有中心约束。这很简单。只需控制从每个标签到底部的拖动。
2) 按住 shift 键,因为您不妨添加我们将要使用的另一个约束,即“底部空间到底部布局指南”。
3)选择“底部空间到底部布局指南”和“在容器中水平居中”。对所有 3 个标签执行此操作。
https://i.stack.imgur.com/HglaO.png
基本上,如果我们取我们希望确定其坐标的标签并将其除以标签总数加 1,那么我们可以将一个数字添加到 IB 以获得动态位置。我正在简化公式,但您可以使用它来设置水平间距或同时设置垂直和水平间距。它超级强大!
这是我们的乘数。
标签1 = 1/4 = .25,
标签2 = 2/4 = .5,
标签3 = 3/4 = .75
(编辑:@Rivera 评论说您可以直接在乘数字段中使用比率,并使用 xCode 进行数学运算!)
https://i.stack.imgur.com/LA7X5.png
5)在属性检查器中选择“第二项”。
6) 从下拉列表中选择“反转第一项和第二项”。
7) 将常数和 wC hAny 值清零。 (如果需要,可以在此处添加偏移量)。
8)这是关键部分:在乘数字段中添加我们的第一个乘数 0.25。
9)当你在它设置顶部的“第一项”为“CenterY”,因为我们想将它居中到标签的 y 中心。这就是所有应该看起来的样子。
https://i.stack.imgur.com/sxtat.png
10) 对每个标签重复此过程并插入相关乘数:Label2 为 0.5,Label3 为 0.75。这是所有紧凑型设备的所有方向的最终产品!超级简单。我一直在研究很多涉及大量代码和间隔的解决方案。这是我在这个问题上看到的最好的解决方案。
更新:@kraftydevil 补充说底部布局指南仅出现在故事板中,而不出现在 xibs 中。在 xibs 中使用“底部空间到容器”。接得好!
https://i.stack.imgur.com/5SQa4.png
因此,我的方法允许您在界面生成器中执行此操作。您所做的是创建您已设置为同等匹配高度的“间隔视图”。然后将顶部和底部约束添加到标签(参见屏幕截图)。
https://i.stack.imgur.com/bJOix.png
更具体地说,我对“Spacer View 1”有一个最高约束,以使用低于 1000 的优先级和高度等于所有其他“间隔视图”的高度约束来超级视图。 “Spacer View 4”对超级视图有底部空间限制。每个标签对其最近的“间隔视图”都有各自的顶部和底部约束。
注意:确保您的标签上没有额外的顶部/底部空间限制以进行超级查看;只是“空间视图”的那些。这将是可以满足的,因为顶部和底部约束分别在“Space View 1”和“Spacer View 4”上。
Duh 1:我复制了我的视图,只是将其置于横向模式,以便您可以看到它有效。
Duh 2:“间隔视图”可能是透明的。
Duh 3:这种方法可以横向应用。
非常快速的界面生成器解决方案:
对于要在超级视图中均匀分布的任意数量的视图,只需为水平布局给每个“对齐中心 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 个视图要留出空间,它看起来像这样:
https://i.stack.imgur.com/l1175.jpg
编辑 注意:选择 N:p
或 p:N
取决于对齐约束的关系顺序。如果“First Item”是Superview.Center,则可以使用p:N
,而如果Superview.Center 是“Second Item”,则可以使用N:p
。如有疑问,请尝试两者... :-)
从 iOS 9 开始,Apple 使用(期待已久的)UIStackView
使这变得非常简单。只需选择要包含在 Interface Builder 中的视图,然后选择 Editor ->嵌入 ->堆栈视图。为堆栈视图设置适当的宽度/高度/边距约束,并确保将 Distribution 属性设置为“等间距”:
https://i.stack.imgur.com/DnKFf.png
当然,如果您需要支持 iOS 8 或更低版本,则必须选择其他选项之一。
我一直在喜欢自动布局并讨厌它的过山车。爱它的关键似乎是接受以下几点:
界面构建器的编辑和“有用的”自动创建约束对所有人来说几乎是无用的,但最微不足道的情况除外 创建类别来简化常见操作是一种救命稻草,因为代码是如此重复和冗长。
也就是说,您所尝试的并不简单,并且很难在界面构建器中实现。在代码中执行非常简单。此代码在 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
中的几个类别方法大大简化了,但为了清楚起见,我在这里做了很长的路要走。
对于感兴趣的人,该类别是 here,它具有沿特定轴均匀分布一组视图的方法。
正确和最简单的方法是使用堆栈视图。
将您的标签/视图添加到堆栈视图:
https://i.stack.imgur.com/tC2wM.jpg
选择 Stack View 并将 Distribution 设置为 Equal Spacing:
https://i.stack.imgur.com/78eRY.jpg
将 Spacing 添加到 Stack View 的最近邻约束并更新帧:
https://i.stack.imgur.com/Bvupl.jpg
为所有标签添加高度约束(可选)。仅对于没有固有大小的视图才需要)。例如,标签在这里不需要高度约束,例如只需要设置 numberOfLines = 3 或 0。
https://i.stack.imgur.com/eqtaU.jpg
享受预览:
https://i.stack.imgur.com/D8eDh.jpg
这些解决方案中的大多数都依赖于奇数个项目,以便您可以取中间项目并将其居中。如果您有偶数个项目但仍希望平均分配怎么办?这是一个更通用的解决方案。此类别将沿垂直或水平轴均匀分布任意数量的项目。
在其超级视图中垂直分布 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
查看开源库 PureLayout。它提供了一些用于分发视图的 API 方法,包括每个视图之间的间距是固定的(视图大小根据需要而变化)和每个视图的大小是固定的(视图之间的间距根据需要而变化)的变体。请注意,所有这些都是在使用任何“间隔视图”的情况下完成的。
// 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
用于约束。)
我遇到了类似的问题并发现了这篇文章。但是,当前提供的答案都没有以您想要的方式解决问题。他们不会使间距相等,而是平均分配标签的中心。重要的是要理解这是不一样的。我已经构建了一个小图表来说明这一点。
https://i.stack.imgur.com/hnHLR.png
有 3 个视图,全部为 20pt 高。使用任何建议的方法均等地分布视图的中心并为您提供图示的布局。请注意,视图的 y 中心是等距的。然而,superview 和 top view 之间的间距是 15pt,而 subview 之间的间距只有 5pt。要使视图等距分布,它们都应为 10pt,即所有蓝色箭头应为 10pt。
尽管如此,我还没有想出一个好的通用解决方案。目前我最好的想法是在子视图之间插入“间距视图”并将间距视图的高度设置为相等。
我能够在 IB 中完全解决这个问题:
进行约束以将每个子视图的中心 Y 与父视图的底部边缘对齐。将每个约束的乘数设置为 1/2n、3/2n、5/2n、...、n-1/2n,其中 n 是您正在分发的子视图的数量。
因此,如果您有三个标签,请将每个约束的乘数设置为 0.1666667、0.5、0.833333。
我找到了一个完美而简单的方法。自动布局不允许您平等地调整空间大小,但它允许您平等地调整视图大小。只需在您的字段之间放置一些不可见的视图,并告诉自动布局保持它们相同的大小。它完美地工作!
https://i.stack.imgur.com/vuYgl.png
https://i.stack.imgur.com/EeRNV.png
不过要注意一件事;当我在界面设计器中减小尺寸时,有时它会感到困惑并在原来的位置留下一个标签,如果尺寸更改为奇数,它就会发生冲突。否则它工作得很好。
编辑:我发现冲突成了一个问题。因此,我采用了一个间距约束,将其删除并用两个约束替换它,一个大于或等于和一个小于或等于。两者大小相同,优先级远低于其他约束。结果没有进一步的冲突。
基于 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;
}
检查 https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/AutoLayoutbyExample/AutoLayoutbyExample.html 对解决您的问题有很好的描述。
使用标签至少可以正常工作:
@"H:|-15-[first(==second)]-[second(==third)]-[third(==first)]-15-|
如果第一个与第二个具有相同的宽度,第二个与第三个具有相同的宽度,第三个与第一个具有相同的宽度,那么它们都将具有相同的宽度……您可以水平(H)和垂直(V)。
迅捷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)
我知道距离第一个答案已经有一段时间了,但我刚刚遇到了同样的问题,我想分享我的解决方案。为子孙后代...
我在 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 上,首先删除所有约束,然后创建视图字典,然后计算要在视图之间使用的空间。之后,我只使用 Visual Language Format 来设置约束:
- (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];
}
这种方法的好处是你只需要做很少的数学运算。我并不是说这是完美的解决方案,但我为我试图实现的布局工作。
我希望它有所帮助。
这是一个将任意数量的子视图垂直居中的解决方案,即使它们具有唯一的大小。您要做的是制作一个中级容器,将其置于超级视图的中心,然后将所有子视图放入容器中并相互排列。但至关重要的是,您还需要将它们限制在容器的顶部和底部,以便容器可以正确调整大小并在超级视图中居中。通过从其子视图中计算出正确的高度,容器可以垂直居中。
在此示例中,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;
}
}
我刚刚使用乘数功能解决了我的问题。我不确定它是否适用于所有情况,但对我来说效果很好。我在 Xcode 6.3 仅供参考。
我最终做的是:
1) 首先将我的按钮放置在 320px 宽度的屏幕上,按照我希望它在 320px 设备上的显示方式分布。
https://i.stack.imgur.com/znRFu.png
2)然后我在所有按钮的超级视图中添加了一个领先的空间约束。
https://i.stack.imgur.com/Yh14k.png
3) 然后我修改了前导空格的属性,使常数为 0,乘数为 x 偏移除以屏幕宽度(例如,我的第一个按钮距离左边缘 8px,所以我将乘数设置为 8/320)
4)那么这里重要的一步就是将约束关系中的第二个Item改为superview.Trailing而不是superview.leading。这是关键,因为 superview.Leading 为 0,在我的情况下为 320,因此 8/320 在 320px 设备上为 8 px,然后当 superview 的宽度更改为 640 或其他任何值时,视图都以相对于宽度的比例移动320px 的屏幕尺寸。这里的数学更容易理解。
https://i.stack.imgur.com/u86IK.png
许多答案不正确,但得到很多计数。这里我只是以编程方式编写了一个解决方案,三个视图是水平对齐的,没有使用间隔视图,但它只有在故事板中使用时知道标签的宽度时才有效。
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]];
这是另一个答案。我正在回答一个类似的问题,并看到引用此问题的链接。我没有看到任何类似于我的答案。所以,我想把它写在这里。
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
}
}
https://i.stack.imgur.com/lpbA9.png
同样,这也可以通过 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 减少了一些代码并且看起来不错。
另一种方法可能是让顶部和底部标签分别具有相对于视图顶部和底部的约束,并且让中间视图分别具有相对于第一和第三视图的顶部和底部约束。
请注意,您对约束的控制比通过将视图彼此靠近拖动直到出现引导虚线来实现的控制更多 - 这些表示将形成的两个对象之间的约束,而不是对象和父视图之间的约束。
在这种情况下,您可能希望将约束更改为“大于或等于”所需值,而不是“等于”以允许它们调整大小。不确定这是否会完全符合您的要求。
在 InterfaceBuilder 中解决这个问题的非常简单的方法:
将居中标签(label2)设置为“Horizontal Center in Container”和“Vertical Center in Container”
选择居中标签和顶部标签 (label1 + label2) 并为垂直间距添加两个约束。一个大于或等于最小间距。一个小于或等于最大间距。
居中标签和底部标签 (label2 + label3) 相同。
此外,您还可以向 label1 添加两个约束 - 到 SuperView 的顶部空间和到 label2 的两个约束 - 到 SuperView 的底部空间。
结果将是所有 4 个间距都将同样地改变它们的大小。
我做了一个可能有帮助的功能。此用法示例:
[self.view addConstraints: [NSLayoutConstraint fluidConstraintWithItems:NSDictionaryOfVariableBindings(button1, button2, button3)
asString:@[@"button1", @"button2", @"button3"]
alignAxis:@"V"
verticalMargin:100
horizontalMargin:50
innerMargin:25]];
将导致 vertical distribution (抱歉没有 10 名嵌入图像)。如果您更改轴和一些边距值:
alignAxis:@"H"
verticalMargin:120
horizontalMargin:20
innerMargin:10
你会得到那个horizontal distribution。
我是 iOS 的新手,但瞧!
均匀分布.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
均匀分布.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
是的,您可以仅在界面生成器中执行此操作而无需编写代码 - 需要注意的是您正在调整标签大小而不是分配空白。在这种情况下,将标签 2 的 X 和 Y 与超级视图对齐,使其固定在中心。然后将标签 1 的垂直空间设置为超级视图并将标签 2 设置为标准,对标签 3 重复此操作。设置标签 2 后,设置标签 1 和 3 的最简单方法是调整它们的大小直到它们对齐。
https://i.stack.imgur.com/e9Ivu.png
https://i.stack.imgur.com/wHMJJ.png
我意识到由于标签之间的标准空间和超级视图之间的标准空间之间的差异,它们在基线之间并不是绝对 100% 等间距的。如果这让您感到困扰,请将大小设置为 0 而不是标准
我只为第一项设置宽度值(>= 宽度)和每个项目之间的最小距离(>= 距离)。然后我使用 Ctrl 将第二个、第三个...项目拖到第一个项目上,以链接项目之间的依赖关系。
https://i.stack.imgur.com/TYw1V.png
聚会迟到了,但我有一个可行的解决方案,可以用间距水平创建菜单。可以使用 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;
}
我想水平对齐 5 张图像,所以我最终遵循了 Mete 的 response,但差别很小。
第一个图像将在容器中水平居中,等于 0 和 1:5 的乘数:
https://i.stack.imgur.com/A3Xvo.png
https://i.stack.imgur.com/5Uhna.png
https://i.stack.imgur.com/x2Roo.png
正如 Mete 解释的那样,顺序是 1、3、5、7、9 等。位置遵循相同的逻辑:第一个位置是 1,然后是空格,然后是下一个位置 3,依此类推。
Android 有一种在我想模仿的基于约束的布局系统中将视图链接在一起的方法。搜索把我带到了这里,但没有一个答案很有效。我不想使用 StackViews,因为它们往往会给我带来更多的悲伤,而不是它们预先保存的内容。我最终创建了一个使用放置在视图之间的 UILayoutGuides 的解决方案。控制它们的宽度允许不同类型的分布,Android 用语中的链样式。该函数接受前导和尾随锚而不是父视图。这允许将链放置在两个任意视图之间,而不是分布在父视图内部。它确实使用了仅在 iOS 9+ 中可用的 UILayoutGuide
,但这应该不再是问题了。
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
}
您可以只创建一个 tableView 并制作 isScrollEnabled = false
。