自动布局让我的生活很困难。从理论上讲,当我转换的时候,它会非常有用,但我似乎一直在与它作斗争。
我做了一个演示项目来寻求帮助。有人知道如何使视图之间的空间增加或减少均匀,每当视图调整大小?
下面是三个标签(手动垂直对齐):
我想要的是,当我旋转时,它们均匀地调整间距(而不是视图大小)。默认情况下,顶部和底部视图向中心挤压:
自动布局让我的生活很困难。从理论上讲,当我转换的时候,它会非常有用,但我似乎一直在与它作斗争。
我做了一个演示项目来寻求帮助。有人知道如何使视图之间的空间增加或减少均匀,每当视图调整大小?
下面是三个标签(手动垂直对齐):
我想要的是,当我旋转时,它们均匀地调整间距(而不是视图大小)。默认情况下,顶部和底部视图向中心挤压:
当前回答
是的,你可以只在接口构建器中这样做,而不需要编写代码——需要注意的是,你是在调整标签的大小,而不是分配空白。在本例中,将标签2的X和Y对齐到父视图,使其固定在中心。然后将标签1的垂直空间设置为superview,标签2设置为standard,重复标签3。在设置标签2之后,设置标签1和3最简单的方法是调整它们的大小,直到它们断开。
这是水平显示,注意标签1和2之间的垂直空间被设置为标准:
这是竖屏版本:
我意识到,由于标签之间的标准空间和父视图的标准空间之间的差异,它们在基线之间不是绝对100%等间距的。如果这让您感到困扰,可以将大小设置为0而不是standard
其他回答
许多答案是不正确的,但得到许多计数。这里我只是编写了一个解决方案,三个视图是水平对齐的,不使用间隔视图,但它只在标签宽度已知的情况下工作,当在故事板中使用。
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]];
正确和最简单的方法是使用堆栈视图。
将标签/视图添加到堆栈视图:
选择堆栈视图并设置分布为相等间距:
在堆栈视图中添加最近邻居约束的空格,并更新帧:
为所有标签添加高度约束(可选)。仅对没有固有大小的视图需要)。例如,标签在这里不需要高度约束,只需要设置numberOfLines = 3或0。
享受预览:
Swift 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)
Android has a method of chaining views together in its constraint based layout system that I wanted to mimic. Searches brought me here but none of the answers quite worked. I didn't want to use StackViews because they tend to cause me more grief down the line than they save up front. I ended up creating a solution that used UILayoutGuides placed between the views. Controlling their width's allows different types of distributions, chain styles in Android parlance. The function accepts a leading and trailing anchor instead of a parent view. This allows the chain to be placed between two arbitrary views rather than distributed inside of the parent view. It does use UILayoutGuide which is only available in iOS 9+ but that shouldn't be a problem anymore.
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
}
这里还有另一个答案。我在回答一个类似的问题时看到了这个问题的链接。我没有看到任何与我类似的答案。所以我想写在这里。
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减少了一些代码,看起来不错。