我在UIStackView中安排了子视图的布局时遇到了问题,并且想知道是否有人可以帮我理解发生了什么。
所以我有一些间距的UIStackView(例如1,但这没关系)和.fillProportionally分布。我添加的排列子视图只有1x1的intrinsicContentSize(可能是任何东西,只是方形视图),我需要在stackView中按比例拉伸它们。
问题是,如果我添加没有实际框架的视图,只有内在大小,那么我会得到错误的布局
否则,如果我使用相同大小的帧添加视图,一切都按预期工作,
但我真的不想设置视图框架。
我非常确定这是关于拥抱和抗压力优先的全部,但无法弄清楚正确的答案是什么。
这是一个游乐场示例:
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
答案 0 :(得分:20)
这显然是UIStackView
实施中的一个错误(即系统错误)。
DonMag 已经在他的评论中指出了正确方向的提示:
当您将堆栈视图的spacing
设置为0时,一切都按预期工作。但是当你将它设置为任何其他值时,布局就会中断。
ℹ️为简单起见,我假设堆栈视图有
使用.fillProportionally
发布UIStackView
创建系统约束,如下所示:
对于每个已排列的子视图,它会添加 等宽 约束(UISV-fill-proportionally
),该约束与堆栈视图本身相关,并带有 乘法器 强>:
arrangedSubview[i].width = multiplier[i] * stackView.width
如果在堆栈视图中有 n 排列的子视图,则会得到 n 这些约束。我们称之为proportionalConstraint[i]
(其中i
表示arrangedSubviews
数组中相应视图的位置。
这些约束不是必需的(即它们的优先级不是1000)。相反,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
的 n-1 约束:
arrangedSubview[i].trailing + spacing = arrangedSubview[i+1].leading
这些约束是必需的(即优先级= 1000)。
(系统还会创建一些其他约束(例如,对于垂直轴以及将第一个和最后一个排列的子视图固定到堆栈视图的边缘)但我不会在这里详细说明,因为它们与理解出错是不相关的。)
.fillProportionally
发布状态
堆栈视图调整其排列视图大小的布局,以便它们沿堆栈视图的轴填充可用空间。视图根据堆栈视图轴的内在内容大小按比例调整大小。
因此,根据这一点,multiplier
的{{1}}应计算如下 proportionalConstraint
:
spacing = 0
=Σ i totalIntrinsicWidth
intrinsicWidth[i]
= multiplier[i]
/ intrinsicWidth[i]
如果我们的10个排列的子视图都具有相同的固有宽度,则按预期工作:
totalIntrinsicWidth
所有multiplier[i] = 0.1
的。但是,只要我们将间距更改为非零值,proportionalConstraint
的计算就会变得复杂得多,因为必须考虑间距的宽度。我已经完成了数学和multiplier
的公式:
对于按如下方式配置的堆栈视图:
上述等式将产生:
multiplier[i]
您可以通过添加来证明这一点:
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
。
这就是为什么屏幕截图中最后排列的子视图被拉伸的原因。
如果您尝试不同的间距并堆叠视图宽度,您最终会得到各种奇怪的布局。但他们都有一个共同点: 间距始终优先。(如果您将间距设置为更大的值,如30或40,您只会看到前两个或三个排列的子视图,因为其余的空间已完全占用按所需的间距。)
proportionalConstraint
分发仅适用于.fillProportionally
。
对于其他间距,系统会使用不正确的乘数创建约束。
这会将布局分解为
唯一的出路是&#34;误用&#34;普通spacing = 0
具有所需的固定宽度约束作为视图之间的间距。 (通常,为此目的引入了UIView
,但您甚至无法使用它们,因为您无法将布局指南添加到堆栈视图。)
我担心由于这个错误,没有干净的解决方案可以做到这一点。
答案 1 :(得分:2)
至少从Xcode 9.2开始,如果初始化程序和内在内容大小 已注释掉,则操场提供了预期的工作。在这种情况下,即使间距> 1,比例填充也按预期工作。 0
这是间距= 5
的示例这似乎有意义,因为排列的子视图没有内在的内容大小,StackView确定它们的宽度按比例填充指定的轴。
如果只启用了初始化程序(而不是内在的内容大小覆盖),那么我得到了这个,它与操场上的评论不匹配,所以我猜这个行为必须在发布问题后改变:
我不理解这种行为,因为在我看来,使用自动布局时应该忽略手动设置框架。
如果只启用了内在内容大小覆盖(而不是初始化程序),那么我会得到产生此帖子的有问题的图像(此处间距= 5):
基本上,设计现在是冲突的,无法实现,因为由于指定的内在内容大小,视图需要1点宽。这里的总空间应该是
{{1}}由于Mischa指出的最低优先级,最后排列的视图约束被打破了
。
在每像素像素检查时,上面的前23个视图宽度大于1个像素,实际上是2个或3个像素,除了最后一个像素,因此数学不匹配,我没有&#39;知道为什么,可能四舍五入的十进制数?
作为参考,在这种情况下,它的内在大小为宽度为5,但仍然无法满足约束条件。