为什么只有单个视图的UIStackView会按比例填充,并且“布局边距"会导致模棱两可的约束错误? [英] Why does a UIStackView with a single view, fill Proportionally, and Layout Margins causes ambiguous constraint error?

查看:126
本文介绍了为什么只有单个视图的UIStackView会按比例填充,并且“布局边距"会导致模棱两可的约束错误?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下代码是尝试将UIStackView添加到视图控制器,并以很小的余量固定在所有边缘上,并为其添加标签.

我希望StackView在以后添加更多视图时可以使用 .fillProportionally 作为其分发模式.

对于单个排列的子视图,只要分发模式为 .fillProportionally 布局边距,使用然后我得到一个模棱两可的约束错误(如下).此错误的原因是什么?

 覆盖功能funD viewDidLoad(){super.viewDidLoad()让label = UILabel(frame:.zero)label.text ="ABC"让堆栈= UIStackView(arrangedSubviews:[label])stack.translatesAutoresizingMaskIntoConstraints = falsestack.distribution = .fill按比例stack.isLayoutMarginsRelativeArrangement = truestack.layoutMargins = .init(顶部:10,左侧:10,底部:10,右侧:10)view.addSubview(堆栈)NSLayoutConstraint.activate([stack.centerXAnchor.constraint(equalTo:view.centerXAnchor),stack.centerYAnchor.constraint(equalTo:view.centerYAnchor),stack.widthAnchor.constraint(equalTo:view.widthAnchor),stack.heightAnchor.constraint(等于:view.heightAnchor),])} 

}

约束约束错误(

有6个水平堆栈视图,每个视图的宽度设置为200-pts,分布"设置为 .fillProportionally ,并填充一个或两个视图.红色视图的固有宽度为25,绿色视图的固有宽度为75.

第一个堆栈视图具有单个视图,没有布局边距,可按预期填充宽度...红色视图占用100%的空间.

第二个堆栈视图具有两个视图并且没有布局边距,也可以按预期填充...红色视图宽50磅(25%),绿色视图宽150磅(75%).

但是,第三个堆栈视图开始显示该问题.单个视图的比例宽度为100%,即200点...,但是随后会应用布局边距.这会将视图从左侧向左移动10点,但是由于自动布局不会从 first 子视图中减去空间,因此它实际上超出了堆栈视图的边缘10点(因此红色视图仍然是静止 200-pts).

第四个堆栈视图看起来就像我们想要的...按比例填充,每边有10点的边距...红色视图宽50点(占200%的25%)) 但是 ,绿色视图的宽度仅为130点.因此,自动版式给这两个视图分别提供了50点(25%)和150点(75%)的信息,但是 然后它应用了边距 并取走了20点从绿色视图.

对于底部的两个堆栈视图,使用 left:100 right:0 left:0 right:100 的布局边距使其更加明显.同样,对于每一个,红色获得50点(25%),绿色获得150点(75%),但是从绿色中剥夺了100点的利润.


因此,要回答最初的问题,即为什么我们只有一个排列的子视图 布局边距时为什么会得到模棱两可的约束,请看一下堆栈视图3.自动布局无法为Red提供100%的空间 应用边距,因此会引发布局错误.


这是运行以上示例的代码.如果您注释掉第三个堆栈视图,您将不会得到错误:

  ProportionalStackExampleViewController类:UIViewController {让outerStackView:UIStackView = {让v = UIStackView()v.translatesAutoresizingMaskIntoConstraints = falsev.axis = .verticalv.spacing = 8返回v}()让externalStackFrame:UIView = {让v = UIView()v.translatesAutoresizingMaskIntoConstraints = falsev.layer.borderWidth = 0.5v.layer.borderColor = UIColor.blue.cgColor返回v}()让infoLabel:UILabel = {令v = UILabel()v.translatesAutoresizingMaskIntoConstraints = falsev.font = UIFont.systemFont(ofSize:12.0,weight:.light)v.numberOfLines = 0v.textAlignment = .centerv.text =红色视图的固有宽度为25 \ n绿色视图的固有宽度为75 \ n所有水平堆栈视图的宽度均为200磅\ n请轻按任何视图以查看其宽度"返回v}()让sizeLabel:UILabel = {令v = UILabel()v.translatesAutoresizingMaskIntoConstraints = falsev.text =(宽度)"返回v}()让myGreen = UIColor(红色:0,绿色:0.75,蓝色:0,阿尔法:1.0)覆盖func viewDidLoad(){super.viewDidLoad()_ in 1 ... 6 {让lbl = UILabel()lbl.font = UIFont.systemFont(ofSize:12.0,weight:.light)lbl.numberOfLines = 0lbl.textAlignment = .centeroutsideStackView.addArrangedSubview(lbl)让sv = UIStackView()sv.translatesAutoresizingMaskIntoConstraints = falsesv.axis = .horizo​​ntalsv.distribution = .fill按比例sv.spacing = 0outsideStackView.addArrangedSubview(sv)}view.addSubview(infoLabel)view.addSubview(sizeLabel)view.addSubview(outerStackFrame)view.addSubview(outerStackView)让g = view.safeAreaLayoutGuideNSLayoutConstraint.activate([infoLabel.topAnchor.constraint(等于:g.topAnchor,常数:20.0),infoLabel.leadingAnchor.constraint(等于:g.leadingAnchor,常数:20.0),infoLabel.trailingAnchor.constraint(等于:g.trailingAnchor,常数:-20.0),sizeLabel.topAnchor.constraint(等于:infoLabel.bottomAnchor,常数:20.0),sizeLabel.centerXAnchor.constraint(等于:g.centerXAnchor),externalStackView.topAnchor.constraint(等于:sizeLabel.bottomAnchor,常数:20.0),externalStackView.widthAnchor.constraint(equalToConstant:200.0),externalStackView.centerXAnchor.constraint(equalTo:view.centerXAnchor),externalStackFrame.widthAnchor.constraint(equalTo:externalStackView.widthAnchor),externalStackFrame.heightAnchor.constraint(等于:externalStackView.heightAnchor),externalStackFrame.centerXAnchor.constraint(等于:externalStackView.centerXAnchor),externalStackFrame.centerYAnchor.constraint(等于:externalStackView.centerYAnchor),])//StackView 1如果让lbl = outsideStackView.arrangedSubviews [0]为?UILabel,让sv = externalStackView.arrangedSubviews [1]作为?UIStackView {lbl.text =一个视图,没有layoutMargins"var v = ProportionalView()v.w = 25.0v.backgroundColor = .redsv.addArrangedSubview(v)var tg = UITapGestureRecognizer(target:self,action:#selector(showWidth(_ :)))v.addGestureRecognizer(tg)}//StackView 2如果让lbl = outsideStackView.arrangedSubviews [2]作为?UILabel,让sv = externalStackView.arrangedSubviews [3]作为?UIStackView {lbl.text =两个视图,无layoutMargins"var v = ProportionalView()v.w = 25.0v.backgroundColor = .redsv.addArrangedSubview(v)var tg = UITapGestureRecognizer(target:self,action:#selector(showWidth(_ :)))v.addGestureRecognizer(tg)v = ProportionalView()v.w = 75.0v.backgroundColor = myGreensv.addArrangedSubview(v)tg = UITapGestureRecognizer(目标:自我,动作:#selector(showWidth(_ :)))v.addGestureRecognizer(tg)}//注释掉该块以查看自动布局错误消失//StackView 3如果让lbl = outsideStackView.arrangedSubviews [4]作为?UILabel,让sv = externalStackView.arrangedSubviews [5]作为?UIStackView {lbl.text =一个视图\ nlayout左边距:10右边距:10"var v = ProportionalView()v.w = 25.0v.backgroundColor = .redsv.addArrangedSubview(v)sv.isLayoutMarginsRelativeArrangement = truesv.layoutMargins = .init(顶部:0,左侧:10,底部:0,右侧:10)var tg = UITapGestureRecognizer(target:self,action:#selector(showWidth(_ :)))v.addGestureRecognizer(tg)}//StackView 4如果让lbl = outsideStackView.arrangedSubviews [6]作为?UILabel,让sv = externalStackView.arrangedSubviews [7]作为?UIStackView {lbl.text =两个视图\ nlayout左边距:10右边距:10"var v = ProportionalView()v.w = 25.0v.backgroundColor = .redsv.addArrangedSubview(v)var tg = UITapGestureRecognizer(target:self,action:#selector(showWidth(_ :)))v.addGestureRecognizer(tg)v = ProportionalView()v.w = 75.0v.backgroundColor = myGreensv.addArrangedSubview(v)sv.isLayoutMarginsRelativeArrangement = truesv.layoutMargins = .init(顶部:0,左侧:10,底部:0,右侧:10)tg = UITapGestureRecognizer(目标:自我,动作:#selector(showWidth(_ :)))v.addGestureRecognizer(tg)}//StackView 5如果让lbl = outsideStackView.arrangedSubviews [8]作为?UILabel,让sv = outsideStackView.arrangedSubviews [9]作为?UIStackView {lbl.text ="layoutMargins左侧:100右侧:0"var v = ProportionalView()v.w = 25.0v.backgroundColor = .redsv.addArrangedSubview(v)var tg = UITapGestureRecognizer(target:self,action:#selector(showWidth(_ :)))v.addGestureRecognizer(tg)v = ProportionalView()v.w = 75.0v.backgroundColor = myGreensv.addArrangedSubview(v)sv.isLayoutMarginsRelativeArrangement = truesv.layoutMargins = .init(顶部:0,左侧:100,底部:0,右侧:0)tg = UITapGestureRecognizer(目标:自我,动作:#selector(showWidth(_ :)))v.addGestureRecognizer(tg)}//StackView 6如果让lbl = outsideStackView.arrangedSubviews [10]作为?UILabel,让sv = externalStackView.arrangedSubviews [11]如?UIStackView {lbl.text ="layoutMargins左侧:0右侧:100"var v = ProportionalView()v.w = 25.0v.backgroundColor = .redsv.addArrangedSubview(v)var tg = UITapGestureRecognizer(target:self,action:#selector(showWidth(_ :)))v.addGestureRecognizer(tg)v = ProportionalView()v.w = 75.0v.backgroundColor = myGreensv.addArrangedSubview(v)sv.isLayoutMarginsRelativeArrangement = truesv.layoutMargins = .init(顶部:0,左侧:0,底部:0,右侧:100)tg = UITapGestureRecognizer(目标:自我,动作:#selector(showWidth(_ :)))v.addGestureRecognizer(tg)}}@objc func showWidth(_ sender:UITapGestureRecognizer)->空白 {如果让v = sender.view {sizeLabel.text =宽度:\(v.frame.width)"sizeLabel.textColor = v.backgroundColor}}}ProportionalView类:UIView {var w:CGFloat = 1.0覆盖var internalContentSize:CGSize {返回CGSize(宽度:w,高度:40.0)}} 

The following code is an attempt to add a UIStackView to a view controller, pinned on all edges with a little margin, and add a label to it.

I want the StackView to use .fillProportionally as its distribution mode ready for when I add more views into it later on.

It seems that for a single arranged subview whenever the distribution mode is .fillProportionally and layout margins are used then I get an ambiguous constrain error (below). What is the cause of this error?

override func viewDidLoad() {
    super.viewDidLoad()

    let label = UILabel(frame: .zero)
    label.text = "ABC"

    let stack = UIStackView(arrangedSubviews: [label])
    stack.translatesAutoresizingMaskIntoConstraints = false
    stack.distribution = .fillProportionally
    stack.isLayoutMarginsRelativeArrangement = true
    stack.layoutMargins = .init(top: 10, left: 10, bottom: 10, right: 10)
    view.addSubview(stack)

    NSLayoutConstraint.activate([
        stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        stack.widthAnchor.constraint(equalTo: view.widthAnchor),
        stack.heightAnchor.constraint(equalTo: view.heightAnchor),
    ])
}

}

Abiguous constraint error (WTFAutoLayout):

(
    "<NSLayoutConstraint:0x600001a432f0 'UISV-canvas-connection' UILayoutGuide:0x6000000f3100'UIViewLayoutMarginsGuide'.leading == UILabel:0x7ff470913280'ABC'.leading   (active)>",
    "<NSLayoutConstraint:0x600001a423f0 'UISV-canvas-connection' UILayoutGuide:0x6000000f3100'UIViewLayoutMarginsGuide'.trailing == UILabel:0x7ff470913280'ABC'.trailing   (active)>",
    "<NSLayoutConstraint:0x600001a425d0 'UISV-fill-proportionally' UILabel:0x7ff470913280'ABC'.width == UIStackView:0x7ff46d510030.width   (active)>",
    "<NSLayoutConstraint:0x600001a77f70 'UIView-leftMargin-guide-constraint' H:|-(10)-[UILayoutGuide:0x6000000f3100'UIViewLayoutMarginsGuide'](LTR)   (active, names: '|':UIStackView:0x7ff46d510030 )>",
    "<NSLayoutConstraint:0x600001a42940 'UIView-rightMargin-guide-constraint' H:[UILayoutGuide:0x6000000f3100'UIViewLayoutMarginsGuide']-(10)-|(LTR)   (active, names: '|':UIStackView:0x7ff46d510030 )>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600001a423f0 'UISV-canvas-connection' UILayoutGuide:0x6000000f3100'UIViewLayoutMarginsGuide'.trailing == UILabel:0x7ff470913280'ABC'.trailing   (active)>

解决方案

Editing with clarification because my original answer was not entirely correct...

First, the .fillProportionally distribution property of a UIStackView is very often misunderstood.

Second, we get some oddities when a stack view's distribution is .fillProportionally and the stack view's .spacing is not 0, or when the stack view also has .layoutMargins set.

The problem you're hitting is the way auto-layout calculates the proportional sizing.

Based on experimentation, auto-layout calculates the proportional widths of the views and then applies the layout margins, subtracting from the width of the last view to accommodate the space.

This can be easily demonstrated as follows:

There are 6 horizontal stack views, each set to 200-pts wide, Distribution set to .fillProportionally, and filled with either one or two views. The Red views have an intrinsic width of 25, the Green views 75.

The first stack view, with a single view and no layout margins, fills the width as expected... the red view takes up 100% of the space.

The second stack view, with two views and no layout margins, also fills as expected... the red view is 50-pts wide (25%) and the green view is 150-pts wide (75%).

The third stack view, though, starts showing the problem. The single view is give a proportional width of 100%, or 200-pts... but then the layout margins are applied. This shifts the view 10-pts from the left, but because auto-layout doesn't subtract space from the first subview, it actually extends 10-pts past the edge of the stack view (so the red view is still 200-pts wide).

The fourth stack view looks like it's doing what we want... proportional fill with 10-pts margin on each side... the Red view is 50-pts wide (25% of 200) but the Green view is only 130-pts wide. So auto-layout gave the two views 50-pts (25%) and 150-pts (75%) but then it applied the margins and took the 20-pts away from the Green view.

Using layout margins of left: 100 right: 0 or left: 0 right: 100 for the bottom two stack views makes it much more obvious. Again, for each of those, Red gets 50-pts (25%) and Green gets 150-pts (75%), but then the margin of 100-pts gets stripped from Green.


So, to answer the original question about why we get ambiguous constraints when we have a single arranged subview and layout margins, look at stack view 3. Auto-layout could not manage to give Red 100% of the space and apply margins, so it throws the layout error.


Here's the code to run the above example. If you comment-out the third stack view, you won't get the error:

class ProportionalStackExampleViewController: UIViewController {

    let outerStackView: UIStackView = {
        let v = UIStackView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.axis = .vertical
        v.spacing = 8
        return v
    }()

    let outerStackFrame: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.layer.borderWidth = 0.5
        v.layer.borderColor = UIColor.blue.cgColor
        return v
    }()

    let infoLabel: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.font = UIFont.systemFont(ofSize: 12.0, weight: .light)
        v.numberOfLines = 0
        v.textAlignment = .center
        v.text = "Red views have intrinsic width of 25\nGreen views have intrinsic width of 75\nAll horizontal stack views are 200-pts wide\nTap any view to see its width"
        return v
    }()

    let sizeLabel: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.text = "(width)"
        return v
    }()

    let myGreen = UIColor(red: 0, green: 0.75, blue: 0, alpha: 1.0)

    override func viewDidLoad() {
        super.viewDidLoad()

        for _ in 1...6 {
            let lbl = UILabel()
            lbl.font = UIFont.systemFont(ofSize: 12.0, weight: .light)
            lbl.numberOfLines = 0
            lbl.textAlignment = .center
            outerStackView.addArrangedSubview(lbl)
            let sv = UIStackView()
            sv.translatesAutoresizingMaskIntoConstraints = false
            sv.axis = .horizontal
            sv.distribution = .fillProportionally
            sv.spacing = 0
            outerStackView.addArrangedSubview(sv)
        }

        view.addSubview(infoLabel)
        view.addSubview(sizeLabel)
        view.addSubview(outerStackFrame)
        view.addSubview(outerStackView)

        let g = view.safeAreaLayoutGuide

        NSLayoutConstraint.activate([

            infoLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            infoLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            infoLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),

            sizeLabel.topAnchor.constraint(equalTo: infoLabel.bottomAnchor, constant: 20.0),
            sizeLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),

            outerStackView.topAnchor.constraint(equalTo: sizeLabel.bottomAnchor, constant: 20.0),
            outerStackView.widthAnchor.constraint(equalToConstant: 200.0),
            outerStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),

            outerStackFrame.widthAnchor.constraint(equalTo: outerStackView.widthAnchor),
            outerStackFrame.heightAnchor.constraint(equalTo: outerStackView.heightAnchor),
            outerStackFrame.centerXAnchor.constraint(equalTo: outerStackView.centerXAnchor),
            outerStackFrame.centerYAnchor.constraint(equalTo: outerStackView.centerYAnchor),
        ])

        // StackView 1
        if let lbl = outerStackView.arrangedSubviews[0] as? UILabel,
            let sv = outerStackView.arrangedSubviews[1] as? UIStackView {

            lbl.text = "One view, no layoutMargins"

            var v = ProportionalView()
            v.w = 25.0
            v.backgroundColor = .red
            sv.addArrangedSubview(v)

            var tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
            v.addGestureRecognizer(tg)

        }

        // StackView 2
        if let lbl = outerStackView.arrangedSubviews[2] as? UILabel,
            let sv = outerStackView.arrangedSubviews[3] as? UIStackView {

            lbl.text = "Two views, no layoutMargins"

            var v = ProportionalView()
            v.w = 25.0
            v.backgroundColor = .red
            sv.addArrangedSubview(v)

            var tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
            v.addGestureRecognizer(tg)

            v = ProportionalView()
            v.w = 75.0
            v.backgroundColor = myGreen
            sv.addArrangedSubview(v)

            tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
            v.addGestureRecognizer(tg)
        }

        // comment out this block to see the auto-layout error goes away
        // StackView 3
        if let lbl = outerStackView.arrangedSubviews[4] as? UILabel,
            let sv = outerStackView.arrangedSubviews[5] as? UIStackView {

            lbl.text = "One view\nlayoutMargins left: 10 right: 10"

            var v = ProportionalView()
            v.w = 25.0
            v.backgroundColor = .red
            sv.addArrangedSubview(v)

            sv.isLayoutMarginsRelativeArrangement = true
            sv.layoutMargins = .init(top: 0, left: 10, bottom: 0, right: 10)

            var tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
            v.addGestureRecognizer(tg)

        }

        // StackView 4
        if let lbl = outerStackView.arrangedSubviews[6] as? UILabel,
            let sv = outerStackView.arrangedSubviews[7] as? UIStackView {

            lbl.text = "Two views\nlayoutMargins left: 10 right: 10"

            var v = ProportionalView()
            v.w = 25.0
            v.backgroundColor = .red
            sv.addArrangedSubview(v)

            var tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
            v.addGestureRecognizer(tg)

            v = ProportionalView()
            v.w = 75.0
            v.backgroundColor = myGreen
            sv.addArrangedSubview(v)

            sv.isLayoutMarginsRelativeArrangement = true
            sv.layoutMargins = .init(top: 0, left: 10, bottom: 0, right: 10)

            tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
            v.addGestureRecognizer(tg)
        }

        // StackView 5
        if let lbl = outerStackView.arrangedSubviews[8] as? UILabel,
            let sv = outerStackView.arrangedSubviews[9] as? UIStackView {

            lbl.text = "layoutMargins left: 100 right: 0"

            var v = ProportionalView()
            v.w = 25.0
            v.backgroundColor = .red
            sv.addArrangedSubview(v)

            var tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
            v.addGestureRecognizer(tg)

            v = ProportionalView()
            v.w = 75.0
            v.backgroundColor = myGreen
            sv.addArrangedSubview(v)

            sv.isLayoutMarginsRelativeArrangement = true
            sv.layoutMargins = .init(top: 0, left: 100, bottom: 0, right: 0)

            tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
            v.addGestureRecognizer(tg)
        }

        // StackView 6
        if let lbl = outerStackView.arrangedSubviews[10] as? UILabel,
            let sv = outerStackView.arrangedSubviews[11] as? UIStackView {

            lbl.text = "layoutMargins left: 0 right: 100"

            var v = ProportionalView()
            v.w = 25.0
            v.backgroundColor = .red
            sv.addArrangedSubview(v)

            var tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
            v.addGestureRecognizer(tg)

            v = ProportionalView()
            v.w = 75.0
            v.backgroundColor = myGreen
            sv.addArrangedSubview(v)

            sv.isLayoutMarginsRelativeArrangement = true
            sv.layoutMargins = .init(top: 0, left: 0, bottom: 0, right: 100)

            tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
            v.addGestureRecognizer(tg)
        }

    }

    @objc func showWidth(_ sender: UITapGestureRecognizer) -> Void {
        if let v = sender.view {
            sizeLabel.text = "Width: \(v.frame.width)"
            sizeLabel.textColor = v.backgroundColor
        }
    }

}

class ProportionalView: UIView {
    var w: CGFloat = 1.0

    override var intrinsicContentSize: CGSize {
        return CGSize(width: w, height: 40.0)
    }

}

这篇关于为什么只有单个视图的UIStackView会按比例填充,并且“布局边距"会导致模棱两可的约束错误?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆