自动布局使我的生活变得困难。从理论上讲,当我切换时它将非常有用,但是我似乎一直在与之抗争。
我做了一个演示项目,试图寻求帮助。有人知道如何在调整视图大小时使视图之间的间隔均匀地增加或减少吗?
这是三个标签(手动在垂直方向上均匀隔开):
我想要的是让他们在旋转时均匀地调整其间距(而不是视图尺寸)。默认情况下,顶视图和底视图向中心倾斜:
自动布局使我的生活变得困难。从理论上讲,当我切换时它将非常有用,但是我似乎一直在与之抗争。
我做了一个演示项目,试图寻求帮助。有人知道如何在调整视图大小时使视图之间的间隔均匀地增加或减少吗?
这是三个标签(手动在垂直方向上均匀隔开):
我想要的是让他们在旋转时均匀地调整其间距(而不是视图尺寸)。默认情况下,顶视图和底视图向中心倾斜:
Answers:
因此,我的方法允许您在界面生成器中执行此操作。您要做的是创建“间隔视图”,将其设置为相等地匹配高度。然后将顶部和底部约束添加到标签(请参见屏幕截图)。
更具体地说,我对“ Spacer View 1”有一个最高约束,以便以低于1000的优先级的高度约束和与所有其他“ spacer view”相等的高度约束进行超级视图。“ Spacer View 4”具有底部空间约束以进行超级视图。每个标签都有与其最接近的“间隔视图”相对应的顶部和底部约束。
注意:请确保您的标签上没有多余的顶部/底部空间限制以进行查看;只是“空间视图”中的那些。这是可以满足的,因为顶部和底部约束分别位于“ Space View 1”和“ Spacer View 4”上。
Duh 1:我复制了我的视图,只是将其置于横向模式,因此您可以看到它起作用。
2:“间隔视图”可能是透明的。
回答3:这种方法可以水平应用。
瞧,没有间隔!
基于原始答案的评论部分中的建议,尤其是@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个标签执行此操作。
基本上,如果我们希望确定要确定其坐标的标签并将其除以标签总数加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中使用“容器的底部空间”。接得好!
非常快速的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个空间要看,则看起来像这样:
编辑注意:N:p
或p:N
取决于对齐约束的关系顺序。如果“第一项”是Superview.Center,则可以使用p:N
;如果“ Superview.Center”是“第二项”,则可以使用N:p
。如有疑问,请尝试一下... :-)
从iOS 9开始,Apple借助(期待已久)简化了操作UIStackView
。只需选择要包含在Interface Builder中的视图,然后选择Editor-> Embed In-> Stack视图。为堆栈视图设置适当的宽度/高度/边距约束,并确保将“分布”属性设置为“等间距”:
当然,如果您需要支持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
,但是为了清楚起见,我在这里做了很长的路要走。
该类别在这里供有兴趣的人士使用,它具有一种沿特定轴均匀间隔排列视图阵列的方法。
这些解决方案中的大多数都取决于项目数量的奇数,以便您可以取中间的项目并居中。如果您有偶数个项目仍要平均分配怎么办?这是一个更通用的解决方案。此类别将沿垂直轴或水平轴均匀分布任何数量的项目。
在其超级视图内垂直分布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
约束。)
我有一个类似的问题,并发现了这篇文章。但是,当前提供的答案都无法以您想要的方式解决问题。它们不会平均分配间距,而是平均分配标签的中心。重要的是要了解这是不一样的。我已经构建了一个小图来说明这一点。
有3个意见,都是20pt高。使用任何建议的方法均等地分布视图的中心,并为您提供插图的布局。请注意,视图的y中心间距相等。但是,超级视图和顶视图之间的间隔为15pt,而子视图之间的间隔仅为5pt。为了使视图均等,它们都应为10pt,即所有蓝色箭头均应为10pt。
不过,我还没有想出一个好的通用解决方案。目前,我最好的想法是在子视图之间插入“间距视图”,并将间距视图的高度设置为相等。
我能够在IB中完全解决此问题:
因此,如果您有三个标签,请将每个约束的乘数设置为0.1666667、0.5、0.833333。
我找到了一种完美而简单的方法。自动布局不允许您平均调整空间大小,但可以让您平均调整视图大小。只需在您的字段之间放置一些不可见的视图,并告诉自动布局以使它们保持相同大小即可。它完美地工作!
值得注意的是;当我在界面设计器中减小尺寸时,有时会感到困惑,并在原来的位置留下标签,如果将尺寸更改为奇数,则会发生冲突。否则,它会完美运行。
编辑:我发现冲突成为一个问题。因此,我选择了一个间距约束,将其删除并用两个约束替换,即大于或等于和小于或等于。两者的大小相同,并且优先级比其他限制要低得多。结果没有进一步的冲突。
在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,其中包含有关解决问题的详细说明。
迅捷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上,首先删除所有约束,然后创建视图字典,然后计算视图之间要使用的空间。之后,我仅使用可视语言格式来设置约束:
- (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 FYI。
我最终要做的是:
1)首先将按钮放置在320像素宽的屏幕上,按照我希望它在320像素设备上的显示方式进行分配。
2)然后,我在所有按钮上添加了领先的Space约束以进行超级视图。
3)然后,我修改了前导空间的属性,以使常数为0,乘数为x偏移除以屏幕的宽度(例如,我的第一个按钮距左边缘为8px,因此将乘数设置为8/320)
4)然后,这里的重要步骤是将约束关系中的第二个项目更改为superview.Trailing而不是superview.leading。这是关键,因为Superview.Leading为0且在我的情况下为320,所以在320px的设备上8/320为8 px,然后当Superview的宽度更改为640或其他值时,所有视图均以相对于宽度的比率移动320像素的屏幕尺寸。这里的数学更容易理解。
许多答案是不正确的,但是却有很多计数。在这里,我只是以编程方式编写一个解决方案,这三个视图是水平对齐的,没有使用间隔视图,但是仅在情节提要中使用标签的宽度已知时才起作用。
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
}
}
同样,使用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)设置为“容器中的水平中心”和“容器中的垂直中心”
选择居中标签和顶部标签(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]];
会导致垂直分布(对不起,没有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
我仅为第一个项目设置宽度值(> =宽度),并为每个项目之间设置最小距离(> =距离)。然后,我使用Ctrl拖动第一个,第二个,第三个...项目,以在各项目之间建立依赖关系。
聚会晚了,但是我有一个有效的解决方案,可以水平间隔创建菜单。使用==
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;
}
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
}
我想水平对齐5张图像,所以最终我跟随Mete的回应有一点点差异。
第一张图片将在等于0且乘数为1:5的容器中水平居中:
其余的图像都一样。例如,第五张(也是最后一张)图像将在等于0且乘数为9:5的容器中水平居中:
正如Mete所解释的,顺序为1、3、5、7、9等。这些位置遵循相同的逻辑:第一个位置为1,然后为空格,第二个位置为3,依此类推。