我在 UIStackView 中排列 subview 的布局遇到问题,想知道是否有人可以帮助我了解发生了什么。
所以我有一些间距(例如 1,但这并不重要)和 .fillProportionally 分布的 UIStackView。我正在添加只有 1x1 的内在内容大小(可以是任何东西,只是方形 View )的排列 subview ,我需要它们在 stackView 中按比例拉伸(stretch)。
问题是,如果我添加没有实际框架的 View ,只有内在尺寸,那么我会得到这个错误的布局
否则,如果我添加具有相同大小框架的 View ,一切都会按预期进行,
但我真的不想设置 View 的框架。
我很确定这完全是关于拥抱和抗压优先,但无法弄清楚正确的答案是什么。
这是一个游乐场示例:
import UIKit
import PlaygroundSupport
class LView: UIView {
// If comment this and leave only intrinsicContentSize - result is wrong
convenience init() {
self.init(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
}
// If comment this and leave only convenience init(), then everything works as expected
public override var intrinsicContentSize: CGSize {
return CGSize(width: 1, height: 1)
}
}
let container = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
container.backgroundColor = UIColor.white
let sv = UIStackView()
container.addSubview(sv)
sv.leftAnchor.constraint(equalTo: container.leftAnchor).isActive = true
sv.rightAnchor.constraint(equalTo: container.rightAnchor).isActive = true
sv.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
sv.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true
sv.translatesAutoresizingMaskIntoConstraints = false
sv.spacing = 1
sv.distribution = .fillProportionally
// Adding arranged subviews to stackView, 24 elements with intrinsic size 1x1
for i in 0..<24 {
let a = LView()
a.backgroundColor = (i%2 == 0 ? UIColor.red : UIColor.blue)
sv.addArrangedSubview(a)
}
sv.layoutIfNeeded()
PlaygroundPage.current.liveView = container
最佳答案
这显然是UIStackView
的实现中的一个bug (即系统错误)。
DonMag 在他的评论中已经给出了指向正确方向的提示:
当您设置堆栈 View 的 spacing
时到 0,一切都按预期工作。但是当您将其设置为任何其他值时,布局会中断。
原因如下:
ℹ️ 为简单起见,我假设堆栈 View 具有
与
.fillProportionally
分销UIStackView
创建系统约束如下:UISV-fill-proportionally
) 乘数 :arrangedSubview[i].width = multiplier[i] * stackView.width
如果您在堆栈 View 中有 n 个排列的 subview ,您将获得 n 个这些约束。让我们调用他们
proportionalConstraint[i]
(其中 i
表示相应 View 在 arrangedSubviews
数组中的位置)。 arrangedSubviews
中第一个元素的约束数组分配的优先级为 999,第二个分配的优先级为 998,依此类推:proportionalConstraint[0].priority = 999
proportionalConstraint[1].priority = 998
proportionalConstraint[2].priority = 997
proportionalConstraint[3].priority = 996
...
proportionalConstraint[n–1].priority = 1000 – n
这意味着 所需的约束总是胜过这些比例约束 !
UISV-spacing
:arrangedSubview[i].trailing + spacing = arrangedSubview[i+1].leading
这些约束是必需的(即优先级 = 1000)。
Apple's documentation在
.fillProportionally
分布状态:A layout where the stack view resizes its arranged views so that they fill the available space along the stack view’s axis. Views are resized proportionally based on their intrinsic content size along the stack view’s axis.
所以根据这个
multiplier
为 proportionalConstraint
s 应按如下方式计算 为 spacing = 0
:totalIntrinsicWidth
= ∑i intrinsicWidth[i]
multiplier[i]
= intrinsicWidth[i]
/totalIntrinsicWidth
如果我们排列的 10 个 subview 都具有相同的内在宽度,这将按预期工作:
multiplier[i] = 0.1
所有
proportionalConstraint
s。但是,一旦我们将间距更改为非零值,multiplier
的计算变得更加复杂,因为必须考虑间距的宽度。我已经完成了 multiplier[i]
的数学和公式是:例子:
对于配置如下的堆栈 View :
上面的等式将产生:
multiplier[i] = 0.0955
您可以通过将其相加来证明这是正确的:
(10 * width) + (9 * spacing)
= (10 * multiplier * stackViewWidth) + (9 * spacing)
= (10 * 0.0955 * 400) + (9 * 2)
= (0.955 * 400) + 18
= 382 + 18
= 400
= stackViewWidth
但是,系统会分配不同的值:
multiplier[i] = 0.0917431
总宽度为
(10 * width) + (9 * spacing)
= (10 * 0.0917431 * 400) + (9 * 2)
= 384,97
< stackViewWidth
显然,这个值是错误的。
因此,系统必须打破约束。当然,它打破了具有最低优先级的约束,即
proportionalConstraint
最后排列的 subview 项。这就是屏幕截图中最后一个排列的 subview 被拉伸(stretch)的原因。
如果您尝试不同的间距和堆栈 View 宽度,您最终会得到各种看起来很奇怪的布局。但他们都有一个共同点:
间距始终优先。 (如果您将间距设置为更大的值,例如 30 或 40,您将只会看到前两个或三个排列的 subview ,因为其余空间已被所需的间距完全占据。)
总结一下:
.fillProportionally
分发仅适用于 spacing = 0
.对于其他间距,系统会使用不正确的乘数创建约束。
这打破了布局
解决这个问题的唯一方法是“滥用”普通
UIView
s 具有所需的固定宽度约束作为 View 之间的间距。 (通常, UILayoutGuide
是为此目的引入的,但您甚至不能使用它们,因为您无法向堆栈 View 添加布局指南。)恐怕由于这个错误,没有干净的解决方案来做到这一点。
关于ios - UIStackView 比例布局,仅具有内在内容大小,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46253248/