UIStackView 的约束在设置间距时中断

Posted

技术标签:

【中文标题】UIStackView 的约束在设置间距时中断【英文标题】:UIStackView's constraints break when spacing is set 【发布时间】:2017-01-20 11:14:40 【问题描述】:

当我尝试使用 UIStackView 时,XCode 抛出以下约束错误:

(
    "<NSAutoresizingMaskLayoutConstraint:0x7f87a1dfa360 h=--& v=--& V:[UIStackView:0x7f87a6403a00(0)]>",
    "<NSLayoutConstraint:0x7f87a6410590 'UISV-canvas-connection' UIStackView:0x7f87a6403a00.top == UIView:0x7f87a6409630.top>",
    "<NSLayoutConstraint:0x7f87a6444170 'UISV-canvas-connection' V:[UIView:0x7f87a644d790]-(0)-|   (Names: '|':UIStackView:0x7f87a6403a00 )>",
    "<NSLayoutConstraint:0x7f87a645bec0 'UISV-fill-equally' UIView:0x7f87a6407a10.height == UIView:0x7f87a6409630.height>",
    "<NSLayoutConstraint:0x7f87a6458f40 'UISV-fill-equally' UIView:0x7f87a644d790.height == UIView:0x7f87a6409630.height>",
    "<NSLayoutConstraint:0x7f87a64306d0 'UISV-spacing' V:[UIView:0x7f87a6409630]-(5)-[UIView:0x7f87a6407a10]>",
    "<NSLayoutConstraint:0x7f87a643bea0 'UISV-spacing' V:[UIView:0x7f87a6407a10]-(5)-[UIView:0x7f87a644d790]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7f87a643bea0 'UISV-spacing' V:[UIView:0x7f87a6407a10]-(5)-[UIView:0x7f87a644d790]>

我的视图控制器如下:

public class ExampleController: UIViewController 

    let v1 = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 100))

    let v2 = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 300))

    let v3 = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 200))

    let parent1 = UIStackView()


    public override func viewDidLoad() 
        super.viewDidLoad()

        v1.backgroundColor = .red
        v2.backgroundColor = .green
        v3.backgroundColor = .blue

        parent1.axis = .vertical
        parent1.distribution = .fillEqually
        parent1.spacing = 5 // TODO: This causes the error!

        parent1.addArrangedSubview(v1)
        parent1.addArrangedSubview(v2)
        parent1.addArrangedSubview(v3)

        view.addSubview(parent1)
    

    // MARK: Constraints

    private var didUpdateConstraints = false

    override public func updateViewConstraints() 
        if !didUpdateConstraints 

            parent1.snp.makeConstraints  (make) -> Void in
                make.edges.equalToSuperview()
            

            didUpdateConstraints = true
        
        super.updateViewConstraints()
    

堆栈视图的分布似乎没有什么不同。每当设置间距时,我都会收到错误消息。


    与我的约束有什么冲突?

    什么是UISV-canvas-connection

【问题讨论】:

UISV-canvas-connectionUIScrollView 与顶部的约束。 堆栈视图的一个很好的方法是只设置您要添加的新项目的高度。允许封闭堆栈视图和滚动视图自动处理宽度/等。 @sdasdadas 相对于self.view,您是否向parent1 添加了约束? 【参考方案1】:

实际上并没有因为间距而破裂。由于您创建视图的方式,它正在破坏:D

当您创建类似...的视图时

let v1 = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 100))

当视图被布局时,它会添加一组默认的约束。 UIStackView 也是如此。

当你创建它时...

let parent1 = UIStackView()

它得到一个 (0, 0, 0, 0) 的帧,然后添加约束以保持该帧的位置和高度。

您得到的错误是由于堆栈视图的高度和宽度。

<NSAutoresizingMaskLayoutConstraint:0x7f87a1dfa360 h=--& v=--& V:[UIStackView:0x7f87a6403a00(0)]>

这一行指的是那些“自动”约束。

您最好的选择是使用约束重新创建视图的宽度和高度。

像这样……

// using this format means you can create the view and add all the
// other config to it at the same time
let v1: UIView = 
    let u = UIView()

    // this line stops the "automatic" constraints being added
    u.translatesAutoresizingMaskIntoConstraints = false

    u.backgroundColor = .red

    // now you have to add your own constraints though
    u.heightAnchor.constraint(equalToConstant: 100).isActive = true
    u.widthAnchor.constraint(equalToConstant: 250).isActive = true
    return u
()

let v2: UIView = 
    let u = UIView()
    u.translatesAutoresizingMaskIntoConstraints = false
    u.backgroundColor = .green
    u.heightAnchor.constraint(equalToConstant: 100).isActive = true
    u.widthAnchor.constraint(equalToConstant: 250).isActive = true
    return u
()

let v3: UIView = 
    let u = UIView()
    u.translatesAutoresizingMaskIntoConstraints = false
    u.backgroundColor = .blue
    u.heightAnchor.constraint(equalToConstant: 100).isActive = true
    u.widthAnchor.constraint(equalToConstant: 250).isActive = true
    return u
()

let parent1: UIStackView = 
    let s = UIStackView()
    s.translatesAutoresizingMaskIntoConstraints = false
    s.spacing = 5
    s.axis = .vertical
    s.distirbution = .fillEqually
    return s
()

public override func viewDidLoad() 
    super.viewDidLoad()

    // I moved all the config stuff into the creation of each view...
    // No need to have them here.

    parent1.addArrangedSubview(v1)
    parent1.addArrangedSubview(v2)
    parent1.addArrangedSubview(v3)

    view.addSubview(parent1)

这里有一点警告。

您在堆栈视图中使用.fillEqually。这将使每个视图高度彼此相等。这将与您添加到它们的高度约束相冲突。也许你应该完全移除每个视图的约束,让堆栈视图来做布局。

像这样……

let v2: UIView = 
    let u = UIView()
    u.translatesAutoresizingMaskIntoConstraints = false
    u.backgroundColor = .green
    return u
()

【讨论】:

谢谢!更进一步,如果我的视图(v1、v2、v3)实际上是标签,我将如何防止这个问题?由于它们的内在内容大小是自动计算的(afaik),因此无法设置手动约束......并且它们的文本会发生变化,因此无法提前知道它们的宽度。 @sdasdadas 您可以在标签上定义手动约束。它只会覆盖内在内容大小而不会发生冲突。也请在答案末尾查看我的警告词。不要过度限制视图。如果您将标签之类的视图添加到堆栈视图中,只需在没有约束的情况下添加它们,并且仅在需要时添加约束。 我明白了,这是有道理的,谢谢。但是,当我用 UILabel() 替换行 UIView(frame: ...) 时,我收到了类似的错误。 @sdasdadas 你加了translatesAutoresizingMaskIntoConstraints = false @sdasdadas 也许。哦。但是什么时候调用 snapkit 的函数呢?在 viewDidLoad 之后,约束将被渲染。这就是发生错误的地方。【参考方案2】:

我在 Playground 中对其进行了测试,并且可以正常工作。您只需将translatesAutoresizingMaskIntoConstraints 设置为false 并将约束添加到UIStackView

import UIKit
import PlaygroundSupport

public class ExampleController: UIViewController 

    let v1 = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 100))
    let v2 = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 300))
    let v3 = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 200))

    let parent1 = UIStackView()

    public override func viewDidLoad() 
        super.viewDidLoad()
        view.backgroundColor = .white

        v1.backgroundColor = .red
        v2.backgroundColor = .green
        v3.backgroundColor = .blue

        parent1.axis = .vertical
        parent1.distribution = .fillEqually
        parent1.spacing = 5 // Not causing error

        parent1.addArrangedSubview(v1)
        parent1.addArrangedSubview(v2)
        parent1.addArrangedSubview(v3)

        parent1.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(parent1)

        NSLayoutConstraint.activate([
            parent1.topAnchor.constraint(equalTo: view.topAnchor),
            parent1.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            parent1.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            parent1.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])
    



PlaygroundPage.current.liveView = ExampleController()

结果:

【讨论】:

它在这里工作的原因是因为您的视图没有预先确定的大小。在 OP 的情况下,刚刚将该行添加到堆栈视图中将不起作用,因为视图具有预定的大小。【参考方案3】:

我遇到了同样的问题。对我来说,诀窍是在设置约束之前将 UIStackView 的间距设置为 0。设置约束后,我将间距设置为所需的量。这消除了相互冲突的约束。

【讨论】:

以上是关于UIStackView 的约束在设置间距时中断的主要内容,如果未能解决你的问题,请参考以下文章

如何在 UIStackView 中动态设置元素之间的间距?

Xcode (Swift) 中的 UI:我只想要 UIStackView 的折叠功能,而不是标准化间距。还值得用吗?

检测 UIStackView 间距中的点击(项目之间)

UIStackView背景色和自定义间距

如何设置数据断点,当 EAX 寄存器设置为特定值时中断

为变量分配某个值时中断