stackView 的轴更改导致布局错误
Posted
技术标签:
【中文标题】stackView 的轴更改导致布局错误【英文标题】:Axis change of stackView giving layout error 【发布时间】:2018-11-02 20:54:57 【问题描述】:我有一个UIStackView
,它根据其宽度改变轴。它仅包括两个UView
s。有一个非常简单的设置(可以复制/粘贴到默认的 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】:首先 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 尾随)。因为现在您有两个宽度约束
-
(FirstView.leading = StackView Leading & FirstView.trailing = StackView Trailing) == (FirstView Width == StackView.width)
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 更改为较低的优先级,这两个冲突约束都具有高优先级。请参阅我编辑的答案。以上是关于stackView 的轴更改导致布局错误的主要内容,如果未能解决你的问题,请参考以下文章
为啥表格视图单元格中的自动布局会导致表格的内容大小被错误地更改?