UIStackView - 根据儿童自动计算理想尺寸

jun*_*cia 2 uikit swift uistackview

在使用游乐场以UIStackView编程方式播放包含两个标签时,我意识到我正在进行堆栈视图的方式和标签不会自动计算它们的大小.这是我(丑陋)的代码:

import UIKit

let label1 = UILabel()
let label2 = UILabel()
let arrangedViews: [UIView] = [label1, label2]
let view = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))

label1.text = "Text 1"
label2.text = "Text 2"

for case let label as UILabel in arrangedViews {
    label.textColor = .white
    label.sizeToFit()
}

view.backgroundColor = .black
let stackView = UIStackView(arrangedSubviews: arrangedViews)
stackView.axis = .vertical
stackView.distribution = .fill
stackView.layer.borderColor = UIColor.white.cgColor
stackView.layer.borderWidth = 1

var stackViewIdealSize = CGSize()
stackViewIdealSize.width = label1.intrinsicContentSize.width

arrangedViews.forEach{label in stackViewIdealSize.height += label.intrinsicContentSize.height}
stackView.frame.size = stackViewIdealSize

view.addSubview(stackView)
Run Code Online (Sandbox Code Playgroud)

这个代码,除了做这么"简单的事情"之外,仍然以不正确的方式计算大小,因为我的游乐场视图是这样的:

在此输入图像描述

有没有办法让你UIStackView甚至UIView更聪明地计算理想尺寸?

rob*_*off 7

我很惊讶你要自定堆栈视图的任何影响borderColorborderWidth.UIStackView被记录为"非渲染的子类UIView; 也就是说,它不提供任何自己的用户界面."事实上,它采用的是CATransformLayer作为它的层,以及CATransformLayer文档中说"该CALayer由一个层渲染属性会被忽略,包括:backgroundColor,contents,边框样式属性,笔触风格属性等".

无论如何,因为它alignment.fill,堆栈视图创建所需优先级约束,迫使其排列的子视图与自身的宽度相同.以下是这些限制:

<NSLayoutConstraint:0x60800008c210 'UISV-alignment' UILabel:0x7f821e4023d0'Text 1'.leading == UILabel:0x7f821e608ef0'Text 2'.leading   (active)>
<NSLayoutConstraint:0x60800008c2b0 'UISV-alignment' UILabel:0x7f821e4023d0'Text 1'.trailing == UILabel:0x7f821e608ef0'Text 2'.trailing   (active)>
<NSLayoutConstraint:0x60800008c080 'UISV-canvas-connection' UIStackView:0x7f821e40b790.leading == UILabel:0x7f821e4023d0'Text 1'.leading   (active)>
<NSLayoutConstraint:0x60800008c1c0 'UISV-canvas-connection' H:[UILabel:0x7f821e4023d0'Text 1']-(0)-|   (active, names: '|':UIStackView:0x7f821e40b790 )>
Run Code Online (Sandbox Code Playgroud)

每个排列的子视图标签本身都有一个约束,将自己的宽度设置为其固有宽度.但是,默认情况下,此约束不是必需优先级.以下是这些限制:

<NSContentSizeLayoutConstraint:0x6080000b7a00 UILabel:0x7fe385601720'Text 1'.width == 44.5 Hug:250 CompressionResistance:750   (active)>
<NSContentSizeLayoutConstraint:0x6180000abac0 UILabel:0x7fb809d0d810'Text 2'.width == 47 Hug:250 CompressionResistance:750   (active)>
Run Code Online (Sandbox Code Playgroud)

这个约束有两个优先级("Hug"和"CompressionResistance").两者都低于要求的优先级.(所需优先级为1000.)

在没有其他更高优先级的约束的情况下,这些约束将使堆栈视图足够宽以适合其最宽的排列子视图.

您省略的关键步骤是您没有设置stackView.translatesAutoresizingMaskIntoConstraints = false.因为您没有将其设置为false,所以堆栈视图具有必需优先级约束,将其位置和大小设置为其现有值frame.您将其现有值设置frame.size.width为"文本1"标签的固有宽度,但这比"文本2"标签的固有宽度窄.因此堆栈视图为自己提供了一个必需优先级宽度约束,该约束又控制了"Text 2"标签的宽度,迫使它比其固有宽度更窄,因此它会剪切其文本.这是那个约束:

<NSLayoutConstraint:0x60800009a6d0 'UIView-Encapsulated-Layout-Width' UIStackView:0x7fb125c059d0.width == 44.5   (active)>
Run Code Online (Sandbox Code Playgroud)

通过关闭translatesAutoresizingMaskIntoConstraints,您可以告诉堆栈视图不要创建与其现有框架匹配的约束.如果然后使用约束来设置堆栈视图的位置,而不是其大小,则其他约束(如前所述)将能够设置其大小以适合其排列的子视图.

所以这就是我写游乐场的方式:

import UIKit
import PlaygroundSupport

let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = #colorLiteral(red: 0.9568627477, green: 0.6588235497, blue: 0.5450980663, alpha: 1)
PlaygroundPage.current.liveView = view

let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical

for i in 1 ... 2 {
    let label = UILabel()
    label.text = "Text \(i)"
    label.textColor = .white
    stackView.addArrangedSubview(label)
}

view.addSubview(stackView)
view.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
view.topAnchor.constraint(equalTo: stackView.topAnchor).isActive = true
Run Code Online (Sandbox Code Playgroud)

要概述堆栈视图,我们可以添加另一个视图:

let stackOutlineView = UIView()
stackOutlineView.translatesAutoresizingMaskIntoConstraints = false
stackOutlineView.backgroundColor = .clear
stackOutlineView.layer.borderWidth = 1
stackOutlineView.layer.borderColor = UIColor.black.cgColor
view.addSubview(stackOutlineView)
stackView.leadingAnchor.constraint(equalTo: stackOutlineView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: stackOutlineView.trailingAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: stackOutlineView.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: stackOutlineView.bottomAnchor).isActive = true
Run Code Online (Sandbox Code Playgroud)

这是结果:

游乐场结果