仅使用intrinsicContentSize

时间:2017-09-16 11:11:54

标签: ios swift autolayout uistackview

我在UIStackView中安排了子视图的布局时遇到了问题,并且想知道是否有人可以帮我理解发生了什么。

所以我有一些间距的UIStackView(例如1,但这没关系)和.fillProportionally分布。我添加的排列子视图只有1x1的intrinsicContentSize(可能是任何东西,只是方形视图),我需要在stackView中按比例拉伸它们。

问题是,如果我添加没有实际框架的视图,只有内在大小,那么我会得到错误的布局

wrong layout

否则,如果我使用相同大小的帧添加视图,一切都按预期工作,

correct layout

但我真的不想设置视图框架。

我非常确定这是关于拥抱和抗压力优先的全部,但无法弄清楚正确的答案是什么。

这是一个游乐场示例:

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

2 个答案:

答案 0 :(得分:20)

这显然是UIStackView实施中的一个错误(即系统错误)。

DonMag 已经在他的评论中指出了正确方向的提示:

当您将堆栈视图的spacing设置为0时,一切都按预期工作。但是当你将它设置为任何其他值时,布局就会中断。

以下解释原因:

ℹ️为简单起见,我假设堆栈视图有

  • a 水平轴
  • 10个已安排的子视图

使用.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发布状态

Apple's documentation

  

堆栈视图调整其排列视图大小的布局,以便它们沿堆栈视图的轴填充可用空间。视图根据堆栈视图轴的内在内容大小按比例调整大小。

因此,根据这一点,multiplier的{​​{1}}应计算如下 proportionalConstraint

  • spacing = 0 i totalIntrinsicWidth
  • intrinsicWidth[i] = multiplier[i] / intrinsicWidth[i]

如果我们的10个排列的子视图都具有相同的固有宽度,则按预期工作:

totalIntrinsicWidth
所有multiplier[i] = 0.1

。但是,只要我们将间距更改为非零值,proportionalConstraint的计算就会变得复杂得多,因为必须考虑间距的宽度。我已经完成了数学和multiplier的公式:

How the stack view should compute the multiplier

实施例

对于按如下方式配置的堆栈视图:

  • stackView.width = 400
  • stackView.spacing = 2

上述等式将产生:

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

的示例

enter image description here

这似乎有意义,因为排列的子视图没有内在的内容大小,StackView确定它们的宽度按比例填充指定的轴。

如果只启用了初始化程序(而不是内在的内容大小覆盖),那么我得到了这个,它与操场上的评论不匹配,所以我猜这个行为必须在发布问题后改变:

enter image description here

我不理解这种行为,因为在我看来,使用自动布局时应该忽略手动设置框架。

如果只启用了内在内容大小覆盖(而不是初始化程序),那么我会得到产生此帖子的有问题的图像(此处间距= 5): enter image description here

基本上,设计现在是冲突的,无法实现,因为由于指定的内在内容大小,视图需要1点宽。这里的总空间应该是

{{1}}
由于Mischa指出的最低优先级,最后排列的视图约束被打破了

在每像素像素检查时,上面的前23个视图宽度大于1个像素,实际上是2个或3个像素,除了最后一个像素,因此数学不匹配,我没有&#39;知道为什么,可能四舍五入的十进制数?

作为参考,在这种情况下,它的内在大小为宽度为5,但仍然无法满足约束条件。

enter image description here