【问题标题】:Axis change of stackView giving layout errorstackView 的轴更改导致布局错误
【发布时间】:2018-11-02 20:54:57
【问题描述】:

我有一个UIStackView,它根据其宽度改变轴。它仅包括两个UViews。有一个非常简单的设置(可以复制/粘贴到默认的 Xcode 项目):

class ViewController: UIViewController {

    enum DisplayMode {
        case regular
        case compact
    }

    private let stackView = UIStackView(frame: .zero)
    private let firstView = UIView(frame: .zero)
    private let secondView = UIView(frame: .zero)
    private var firstViewWidth: NSLayoutConstraint?
    private var secondViewWidth: NSLayoutConstraint?

    override func viewDidLoad() {
        super.viewDidLoad()

        stackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(stackView)
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: view.topAnchor),
            stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            ])

        firstView.backgroundColor = .red
        stackView.addArrangedSubview(firstView)
        firstView.heightAnchor.constraint(equalToConstant: 80).isActive = true
        firstViewWidth = firstView.widthAnchor.constraint(equalTo: stackView.widthAnchor, multiplier: 1/3)
        firstViewWidth?.isActive = true

        secondView.backgroundColor = .black
        stackView.addArrangedSubview(secondView)
        secondView.heightAnchor.constraint(equalToConstant: 80).isActive = true
        secondViewWidth = secondView.widthAnchor.constraint(equalTo: stackView.widthAnchor, multiplier: 2/3)
        secondViewWidth?.isActive = true
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        if view.bounds.width < 400 {
            switchMode(.compact)
        } else {
            switchMode(.regular)
        }
    }
}

private extension ViewController {

    func switchMode(_ mode: DisplayMode) {
        switch mode {
        case .regular:
            stackView.axis = .horizontal
            firstViewWidth?.isActive = true
            secondViewWidth?.isActive = true
        case .compact:
            stackView.axis = .vertical
            firstViewWidth?.isActive = false
            secondViewWidth?.isActive = false
        }
    }
}

它工作得很好,但它给了我无意义的布局错误(输出中的所有约束似乎都很好),同时从紧凑更改为常规:

(
    "<NSLayoutConstraint:0x6000002c1c20 UIView:0x7fbd4dc149a0.width == 0.333333*UIStackView:0x7fbd4df02430.width   (active)>",
    "<NSLayoutConstraint:0x6000002c2170 UIView:0x7fbd4dc14d90.width == 0.666667*UIStackView:0x7fbd4df02430.width   (active)>",
    "<NSLayoutConstraint:0x6000002f2080 'UISV-canvas-connection' UIStackView:0x7fbd4df02430.leading == UIView:0x7fbd4dc149a0.leading   (active)>",
    "<NSLayoutConstraint:0x6000002f1c70 'UISV-canvas-connection' H:[UIView:0x7fbd4dc14d90]-(0)-|   (active, names: '|':UIStackView:0x7fbd4df02430 )>",
    "<NSLayoutConstraint:0x6000002f05f0 'UISV-spacing' H:[UIView:0x7fbd4dc149a0]-(0)-[UIView:0x7fbd4dc14d90]   (active)>"
)

有人可以解释为什么会这样吗?动态轴是罪魁祸首吗?

【问题讨论】:

    标签: ios autolayout uistackview


    【解决方案1】:

    首先 viewDidLayoutSubviews 不是处理方向变化的好选择。Apple 推荐的方法是 viewWillTransitionToSize。 更多详情:What is the "right" way to handle orientation changes in iOS 8?

    当你第一次运行你的代码时,堆栈视图出现垂直分布,第一视图和第二视图的情况都有以下约束

    第一视图->

             Leading =  StackView Leading,
             Trailing =  StackView Trailing,
             Top      = StackView Top,
             height   =   80
    

    第二视图->

                Leading =  StackView Leading    ,
                  Top      = FirstView Top,
                  Trailing =  StackView Trailing,
                  height   =   80
    

    当您将设备从纵向旋转到横向时,堆栈视图会出现水平分布并激活跟随约束。

    FirstView.Width = StackView.Width * 1/3

    导致 FirstView 与您之前的约束冲突(StackView 前导,StackView 尾随)。因为现在您有两个宽度约束

    1. (FirstView.leading = StackView Leading & FirstView.trailing = StackView Trailing) == (FirstView Width == StackView.width)
    2. FirstView.Width = StackView.Width * 1/3

    为什么 StackView 不自动删除冲突约束之一?

    --> 因为这两个约束都具有高优先级(1000)。如果我们只是将 firstViewWidth 更改为更低(999)并删除 secondViewWidth 约束,那么您的解决方案将起作用

    尝试用以下代码替换您的代码

       import UIKit
    
    class ViewController: UIViewController {
    
        enum DisplayMode {
            case regular
            case compact
        }
    
        private let stackView = UIStackView(frame: .zero)
        private let firstView = UIView(frame: .zero)
        private let secondView = UIView(frame: .zero)
        private var firstViewWidth: NSLayoutConstraint?
        private var secondViewWidth: NSLayoutConstraint?
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            stackView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(stackView)
            NSLayoutConstraint.activate([
                stackView.topAnchor.constraint(equalTo: view.topAnchor),
                stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                ])
    
            firstView.backgroundColor = .red
            stackView.addArrangedSubview(firstView)
            firstView.heightAnchor.constraint(equalToConstant: 80).isActive = true
            firstViewWidth = firstView.widthAnchor.constraint(equalTo: stackView.widthAnchor, multiplier: 1/3)
            firstViewWidth?.priority = UILayoutPriority(rawValue: 999)
            firstViewWidth?.isActive = true
    
            secondView.backgroundColor = .black
            stackView.addArrangedSubview(secondView)
            secondView.heightAnchor.constraint(equalToConstant: 80).isActive = true
    
    
            if self.view.bounds.width < 400 {
                self.switchMode(.compact)
            } else {
                self.switchMode(.regular)
            }
        }
    
        override func viewDidLayoutSubviews() {
            super.viewDidLayoutSubviews()
    
        }
    
        override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
            super.viewWillTransition(to: size, with: coordinator)
    
    
            coordinator.animate(alongsideTransition: { (context) in
    
            }) { (context) in
                if self.view.bounds.width < 400 {
                    self.switchMode(.compact)
                } else {
                    self.switchMode(.regular)
                }
            }
    
        }
    
    }
    
    private extension ViewController {
    
        func switchMode(_ mode: DisplayMode) {
            switch mode {
            case .regular:
                stackView.axis = .horizontal
                firstViewWidth?.isActive = true
    
            case .compact:
                stackView.axis = .vertical
                firstViewWidth?.isActive = false
            }
        }
    }
    

    【讨论】:

    • 感谢您帮助我!虽然我可以看到诀窍 - 删除排列的子视图并将它们添加回来 - 我认为它的解决方法不是正确的解决方案。我不能让它成为一个公认的答案。我仍然没有得到答案的核心 - 为什么 stackview 轴不删除您所说的冲突约束。一旦 stackView 是水平的,它就不应该有 firstView.trailing = stackView.trailing 约束。
    • 嗨@ViktorKucera 感谢您指出我正确的方向。StackView 不会自动删除冲突的约束,因为如果您只需将 firstViewWidth 更改为较低的优先级,这两个冲突约束都具有高优先级。请参阅我编辑的答案。
    猜你喜欢
    • 2018-04-13
    • 2015-08-18
    • 2021-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多