iOS 9.0附带UIStackView,可以根据内容大小更轻松地布局视图。例如,要根据内容宽度连续放置3个按钮,只需将它们嵌入堆栈视图中,设置轴水平和分布 - 按比例填充。
问题是如何在不支持堆栈视图的旧iOS版本中实现相同的结果。
我提出的一个解决方案是粗糙的并且看起来不太好。再次,您将3个按钮连续放置并使用约束将它们固定到最近的邻居。在这之后你显然会看到内容优先模糊错误,因为自动布局系统不知道哪个按钮需要在其他按钮之前增长/缩小。
不幸的是,在应用程序发布之前标题是未知的,所以你可能随意选择一个按钮。让我们说,我已经将中间按钮的水平内容优先级从标准250降低到249.现在它会在其他两个之前增长。另一个问题是左右按钮严格缩小到其内容宽度,而没有像Stack View版本那样漂亮的填充。
答案 0 :(得分:5)
对于这么简单的事情来说似乎过于复杂。但约束的乘数值是只读的,所以你必须采取艰难的方式。
如果我不得不这样做,我会这样做:
在IB中:使用约束创建一个UIView以水平填充superView(例如)
在IB中:添加3个按钮,添加约束以水平对齐。
在代码中:以编程方式在每个UIButton和UIView之间创建1个NSConstraint,其属性为NSLayoutAttributeWidth
,乘数为0.33。
在这里,您将获得3个相同宽度的按钮,使用1/3的UIView宽度。
观察按钮的title
(使用KVO或子类UIButton)。
当标题更改时,请使用以下内容计算按钮内容的大小:
CGSize stringsize = [myButton.title sizeWithAttributes:
@{NSFontAttributeName: [UIFont systemFontOfSize:14.0f]}];
删除所有以编程方式创建的约束。
将每个按钮的计算宽度(步骤4)与UIView的宽度进行比较,并确定每个按钮的比例。
以相同的方式重新创建步骤3的约束,但用步骤6中计算的比率替换0.33,并将它们添加到UI元素。
答案 1 :(得分:4)
是的,我们只能使用约束来获得相同的结果:)
想象一下,我有三个标签:
结构
-- ParentView --
-- UIView -- (replace here the UIStackView)
-- Label 1 --
-- Label 2 --
-- Label 3 --
<强>约束强>
例如UIView有这样的约束: view.leading = superview.leading,view.trailing = superview.trailing,它垂直居中
UILabels约束
SecondLabel.width等于:
firstLabel.width *(secondLabelIntrinsicSizeWidth / firstLabelIntrinsicSizeWidth)
ThirdLabel.width等于:
firstLabel.width *(thirdLabelIntrinsicSizeWidth / firstLabelIntrinsicSizeWidth)
我会回来寻求更多解释
答案 2 :(得分:2)
您可能想要考虑UIStackView的后端,有几个开源项目。这里的好处是,如果您转移到UIStackView,您将获得最少的代码更改。我已经使用了TZStackView并且它的效果令人钦佩。
或者,较轻的解决方案是复制比例堆栈视图布局的逻辑。
将每个视图的宽度设置为等于父堆栈视图乘以其总内在内容宽度的比例。
我在下面附上了一个水平比例堆栈视图的粗略示例,您可以在Swift Playground中运行它。
import UIKit
import XCPlayground
let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
view.layer.borderWidth = 1
view.layer.borderColor = UIColor.grayColor().CGColor
view.backgroundColor = UIColor.whiteColor()
XCPlaygroundPage.currentPage.liveView = view
class ProportionalStackView: UIView {
private var stackViewConstraints = [NSLayoutConstraint]()
var arrangedSubviews: [UIView] {
didSet {
addArrangedSubviews()
setNeedsUpdateConstraints()
}
}
init(arrangedSubviews: [UIView]) {
self.arrangedSubviews = arrangedSubviews
super.init(frame: CGRectZero)
addArrangedSubviews()
}
convenience init() {
self.init(arrangedSubviews: [])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func updateConstraints() {
removeConstraints(stackViewConstraints)
var newConstraints = [NSLayoutConstraint]()
for (n, subview) in arrangedSubviews.enumerate() {
newConstraints += buildVerticalConstraintsForSubview(subview)
if n == 0 {
newConstraints += buildLeadingConstraintsForLeadingSubview(subview)
} else {
newConstraints += buildConstraintsBetweenSubviews(arrangedSubviews[n-1], subviewB: subview)
}
if n == arrangedSubviews.count - 1 {
newConstraints += buildTrailingConstraintsForTrailingSubview(subview)
}
}
// for proportional widths, need to determine contribution of each subview to total content width
let totalIntrinsicWidth = subviews.reduce(0) { $0 + $1.intrinsicContentSize().width }
for subview in arrangedSubviews {
let percentIntrinsicWidth = subview.intrinsicContentSize().width / totalIntrinsicWidth
newConstraints.append(NSLayoutConstraint(item: subview, attribute: .Width, relatedBy: .Equal, toItem: self, attribute: .Width, multiplier: percentIntrinsicWidth, constant: 0))
}
addConstraints(newConstraints)
stackViewConstraints = newConstraints
super.updateConstraints()
}
}
// Helper methods
extension ProportionalStackView {
private func addArrangedSubviews() {
for subview in arrangedSubviews {
if subview.superview != self {
subview.removeFromSuperview()
addSubview(subview)
}
}
}
private func buildVerticalConstraintsForSubview(subview: UIView) -> [NSLayoutConstraint] {
return NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[subview]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["subview": subview])
}
private func buildLeadingConstraintsForLeadingSubview(subview: UIView) -> [NSLayoutConstraint] {
return NSLayoutConstraint.constraintsWithVisualFormat("|-0-[subview]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["subview": subview])
}
private func buildConstraintsBetweenSubviews(subviewA: UIView, subviewB: UIView) -> [NSLayoutConstraint] {
return NSLayoutConstraint.constraintsWithVisualFormat("[subviewA]-0-[subviewB]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["subviewA": subviewA, "subviewB": subviewB])
}
private func buildTrailingConstraintsForTrailingSubview(subview: UIView) -> [NSLayoutConstraint] {
return NSLayoutConstraint.constraintsWithVisualFormat("[subview]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["subview": subview])
}
}
let labelA = UILabel()
labelA.text = "Foo"
let labelB = UILabel()
labelB.text = "FooBar"
let labelC = UILabel()
labelC.text = "FooBarBaz"
let stack = ProportionalStackView(arrangedSubviews: [labelA, labelB, labelC])
stack.translatesAutoresizingMaskIntoConstraints = false
labelA.translatesAutoresizingMaskIntoConstraints = false
labelB.translatesAutoresizingMaskIntoConstraints = false
labelC.translatesAutoresizingMaskIntoConstraints = false
labelA.backgroundColor = UIColor.orangeColor()
labelB.backgroundColor = UIColor.greenColor()
labelC.backgroundColor = UIColor.redColor()
view.addSubview(stack)
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-0-[stack]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["stack": stack]))
view.addConstraint(NSLayoutConstraint(item: stack, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 0))
答案 3 :(得分:2)
使用autolayout对您有利。它可以为你做所有繁重的工作。
这是一个UIViewController
,可以在屏幕截图中显示3 UILabels
,无需计算。有3个UIView
子视图用于为标签“填充”并设置背景颜色。这些UIViews
中的每一个都有UILabel
子视图,只显示文本而不显示任何其他内容。
所有布局都在viewDidLoad
中使用自动布局完成,这意味着没有计算比率或帧,也没有KVO。改变填充和压缩/拥抱优先级等事情是轻而易举的。这也可能避免依赖于TZStackView
等开源解决方案。这在界面构建器中很容易设置,绝对不需要代码。
class StackViewController: UIViewController {
private let leftView: UIView = {
let leftView = UIView()
leftView.translatesAutoresizingMaskIntoConstraints = false
leftView.backgroundColor = .blueColor()
return leftView
}()
private let leftLabel: UILabel = {
let leftLabel = UILabel()
leftLabel.translatesAutoresizingMaskIntoConstraints = false
leftLabel.textColor = .whiteColor()
leftLabel.text = "A medium title"
leftLabel.textAlignment = .Center
return leftLabel
}()
private let middleView: UIView = {
let middleView = UIView()
middleView.translatesAutoresizingMaskIntoConstraints = false
middleView.backgroundColor = .redColor()
return middleView
}()
private let middleLabel: UILabel = {
let middleLabel = UILabel()
middleLabel.translatesAutoresizingMaskIntoConstraints = false
middleLabel.textColor = .whiteColor()
middleLabel.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
middleLabel.textAlignment = .Center
return middleLabel
}()
private let rightView: UIView = {
let rightView = UIView()
rightView.translatesAutoresizingMaskIntoConstraints = false
rightView.backgroundColor = .greenColor()
return rightView
}()
private let rightLabel: UILabel = {
let rightLabel = UILabel()
rightLabel.translatesAutoresizingMaskIntoConstraints = false
rightLabel.textColor = .whiteColor()
rightLabel.text = "OK"
rightLabel.textAlignment = .Center
return rightLabel
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(leftView)
view.addSubview(middleView)
view.addSubview(rightView)
leftView.addSubview(leftLabel)
middleView.addSubview(middleLabel)
rightView.addSubview(rightLabel)
let views: [String : AnyObject] = [
"topLayoutGuide" : topLayoutGuide,
"leftView" : leftView,
"leftLabel" : leftLabel,
"middleView" : middleView,
"middleLabel" : middleLabel,
"rightView" : rightView,
"rightLabel" : rightLabel
]
// Horizontal padding for UILabels inside their respective UIViews
NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-(16)-[leftLabel]-(16)-|", options: [], metrics: nil, views: views))
NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-(16)-[middleLabel]-(16)-|", options: [], metrics: nil, views: views))
NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-(16)-[rightLabel]-(16)-|", options: [], metrics: nil, views: views))
// Vertical padding for UILabels inside their respective UIViews
NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-(6)-[leftLabel]-(6)-|", options: [], metrics: nil, views: views))
NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-(6)-[middleLabel]-(6)-|", options: [], metrics: nil, views: views))
NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-(6)-[rightLabel]-(6)-|", options: [], metrics: nil, views: views))
// Set the views' vertical position. The height can be determined from the label's intrinsic content size, so you only need to specify a y position to layout from. In this case, we specified the top of the screen.
NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[topLayoutGuide][leftView]", options: [], metrics: nil, views: views))
NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[topLayoutGuide][middleView]", options: [], metrics: nil, views: views))
NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[topLayoutGuide][rightView]", options: [], metrics: nil, views: views))
// Horizontal layout of views
NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[leftView][middleView][rightView]|", options: [], metrics: nil, views: views))
// Make sure the middle view is the view that expands to fill up the extra space
middleLabel.setContentHuggingPriority(UILayoutPriorityDefaultLow, forAxis: .Horizontal)
middleView.setContentHuggingPriority(UILayoutPriorityDefaultLow, forAxis: .Horizontal)
}
}