stackView的轴更改导致布局错误

时间:2018-11-02 20:54:58

标签: ios autolayout uistackview

我有一个UIStackView,它根据其宽度更改轴。它仅包含两个UView。有一个非常简单的设置(可以复制/粘贴到默认的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)>"
)

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

1 个答案:

答案 0 :(得分:3)

首先,viewDidLayoutSubviews不是处理方向更改的好选择。Apple推荐的方法是viewWillTransitionToSize。 有关更多详细信息:What is the "right" way to handle orientation changes in iOS 8?

第一次运行代码时,堆栈视图显示为“垂直分布”,这意味着FirstView和Second View都具有以下约束条件

FirstView->

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

SecondView->

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

将设备从“纵向”旋转到“横向”时,堆栈视图将显示为水平分布,并使“跟随约束”处于活动状态。

FirstView.Width = StackView.Width * 1/3

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

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

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

->因为这两个约束都具有较高的优先级(1000)。如果我们仅将firstViewWidth更改为lower(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
        }
    }
}