当 trait 集合发生变化时,就会出现约束冲突,就好像 stackview 轴没有改变一样
Posted
技术标签:
【中文标题】当 trait 集合发生变化时,就会出现约束冲突,就好像 stackview 轴没有改变一样【英文标题】:When trait collection changes, constraint conflicts arise as though the stackview axis didn't change 【发布时间】:2019-01-28 18:08:05 【问题描述】:我有一个带有两个控件的 stackview。
当 UI 不受垂直约束时: Vertical1
当UI垂直受限时:Horizontal1
我得到了如图所示的两个 UI。第一次显示 UI 时没有约束冲突。但是,当我从垂直约束变为垂直 = 常规时,我会遇到约束冲突。
当我注释掉 stackview 空间时(参见下面的代码注释),我没有遇到约束冲突。
class ViewController: UIViewController
var rootStack: UIStackView!
var aggregateStack: UIStackView!
var field1: UITextField!
var field2: UITextField!
var f1f2TrailTrail: NSLayoutConstraint!
override func viewDidLoad()
super.viewDidLoad()
view.backgroundColor = .white
createIntializeViews()
createInitializeAddStacks()
private func createIntializeViews()
field1 = UITextField()
field2 = UITextField()
field1.text = "test 1"
field2.text = "test 2"
private func createInitializeAddStacks()
rootStack = UIStackView()
aggregateStack = UIStackView()
// If I comment out the following, there are no constraint conflicts
aggregateStack.spacing = 2
aggregateStack.addArrangedSubview(field1)
aggregateStack.addArrangedSubview(field2)
rootStack.addArrangedSubview(aggregateStack)
view.addSubview(rootStack)
rootStack.translatesAutoresizingMaskIntoConstraints = false
aggregateStack.translatesAutoresizingMaskIntoConstraints = false
field1.translatesAutoresizingMaskIntoConstraints = false
field2.translatesAutoresizingMaskIntoConstraints = false
f1f2TrailTrail = field2.trailingAnchor.constraint(equalTo: field1.trailingAnchor)
override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.verticalSizeClass == .regular
aggregateStack.axis = .vertical
f1f2TrailTrail.isActive = true
else if traitCollection.verticalSizeClass == .compact
f1f2TrailTrail.isActive = false
aggregateStack.axis = .horizontal
else
print("Unexpected")
约束冲突在这里-
(
"<NSLayoutConstraint:0x600001e7d1d0 UITextField:0x7f80b2035000.trailing == UITextField:0x7f80b201d000.trailing (active)>",
"<NSLayoutConstraint:0x600001e42800 'UISV-spacing' H:[UITextField:0x7f80b201d000]-(2)-[UITextField:0x7f80b2035000] (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600001e42800 'UISV-spacing' H:[UITextField:0x7f80b201d000]-(2)-[UITextField:0x7f80b2035000] (active)>
当我将输出放在 www.wtfautolayout.com 中时,我得到以下信息: Easier to Read Output
上图中显示的第二个约束让我认为在评估约束之前没有发生对 stackview 垂直轴的更改。
谁能告诉我我做错了什么或如何正确设置(最好没有故事板)?
[编辑] 文本字段的后沿对齐以具有此:
More of the form - portrait
More of the form - landscape
【问题讨论】:
为什么要为两个文本字段添加前导/尾随约束?很确定这是导致约束问题的原因。 @DonMag 在完整的实现中,我有几行不同的控件。它是用户使用文本字段、步进器和分段控件填写的表单。当没有垂直约束时,每行的文本字段控件沿它们的后沿对齐。 正在查看您的“更多形式”图像......它看起来像是纵向方向?如果是这样,您希望它在横向中看起来如何?或者,是横向,并且您希望步进器在纵向中移动到 TextField-2 下方? 在横向中,TextField 2、Stepper 和 TextField 3 按此顺序位于同一行。我用横向的图片更新了原始帖子。 这是你想要得到的吗? imgur.com/a/L6KB0Tl 【参考方案1】:情侣笔记...
“嵌套”堆栈视图存在导致约束冲突的固有问题。这可以通过将受影响元素的优先级设置为 999(而不是默认的 1000)来避免。 您的布局变得有点复杂... 标签“附加”到文本字段;元素需要在纵向的两条“线”或横向的一条“线”上;具有不同高度的“多元素线”的一个元素(步进器);等等。 要使“field2”和“field3”大小相等,您需要将它们的宽度限制为相等,即使它们不是同一个子视图的子视图。这是完全有效的,只要它们是同一视图层次结构的后代。 Stackview 很棒 --- 除非它们不是。我几乎建议只使用约束。您需要添加更多约束,但它可能可以避免堆栈视图的一些问题。但是,这里有一个示例可以帮助您上路。
我添加了一个名为LabeledFieldStackView
的UIStackView
子类...它在堆栈视图中设置了Label-above-Field。比在所有其他布局代码中混合它要干净一些。
class LabeledFieldStackView: UIStackView
var theLabel: UILabel =
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
return v
()
var theField: UITextField =
let v = UITextField()
v.translatesAutoresizingMaskIntoConstraints = false
v.borderStyle = .roundedRect
return v
()
convenience init(with labelText: String, fieldText: String, verticalGap: CGFloat)
self.init()
axis = .vertical
alignment = .fill
distribution = .fill
spacing = 2
addArrangedSubview(theLabel)
addArrangedSubview(theField)
theLabel.text = labelText
theField.text = fieldText
self.translatesAutoresizingMaskIntoConstraints = false
class LargentViewController: UIViewController
var rootStack: UIStackView!
var fieldStackView1: LabeledFieldStackView!
var fieldStackView2: LabeledFieldStackView!
var fieldStackView3: LabeledFieldStackView!
var fieldStackView4: LabeledFieldStackView!
var stepper: UIStepper!
var fieldAndStepperStack: UIStackView!
var twoLineStack: UIStackView!
var fieldAndStepperStackWidthConstraint: NSLayoutConstraint!
// horizontal gap between elements on the same "line"
var horizontalSpacing: CGFloat!
// vertical gap between "lines"
var verticalSpacing: CGFloat!
// vertical gap between labels above text fields
var labelToFieldSpacing: CGFloat!
override func viewDidLoad()
super.viewDidLoad()
view.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
horizontalSpacing = CGFloat(2)
verticalSpacing = CGFloat(8)
labelToFieldSpacing = CGFloat(2)
createIntializeViews()
createInitializeStacks()
fillStacks()
private func createIntializeViews()
fieldStackView1 = LabeledFieldStackView(with: "label 1", fieldText: "field 1", verticalGap: labelToFieldSpacing)
fieldStackView2 = LabeledFieldStackView(with: "label 2", fieldText: "field 2", verticalGap: labelToFieldSpacing)
fieldStackView3 = LabeledFieldStackView(with: "label 3", fieldText: "field 3", verticalGap: labelToFieldSpacing)
fieldStackView4 = LabeledFieldStackView(with: "label 4", fieldText: "field 4", verticalGap: labelToFieldSpacing)
stepper = UIStepper()
private func createInitializeStacks()
rootStack = UIStackView()
fieldAndStepperStack = UIStackView()
twoLineStack = UIStackView()
[rootStack, fieldAndStepperStack, twoLineStack].forEach
$0?.translatesAutoresizingMaskIntoConstraints = false
// rootStack has spacing of horizontalSpacing (inter-line vertical spacing)
rootStack.axis = .vertical
rootStack.alignment = .fill
rootStack.distribution = .fill
rootStack.spacing = verticalSpacing
// fieldAndStepperStack has spacing of horizontalSpacing (space between field and stepper)
// and .alignment of .bottom (so stepper aligns vertically with field)
fieldAndStepperStack.axis = .horizontal
fieldAndStepperStack.alignment = .bottom
fieldAndStepperStack.distribution = .fill
fieldAndStepperStack.spacing = horizontalSpacing
// twoLineStack has inter-line vertical spacing of
// verticalSpacing in portrait orientation
// for landscape orientation, the two "lines" will be changed to one "line"
// and the spacing will be changed to horizontalSpacing
twoLineStack.axis = .vertical
twoLineStack.alignment = .leading
twoLineStack.distribution = .fill
twoLineStack.spacing = verticalSpacing
private func fillStacks()
self.view.addSubview(rootStack)
// constrain rootStack Top, Leading, Trailing = 20
// no height or bottom constraint
NSLayoutConstraint.activate([
rootStack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20.0),
rootStack.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20.0),
rootStack.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20.0),
])
rootStack.addArrangedSubview(fieldStackView1)
fieldAndStepperStack.addArrangedSubview(fieldStackView2)
fieldAndStepperStack.addArrangedSubview(stepper)
twoLineStack.addArrangedSubview(fieldAndStepperStack)
twoLineStack.addArrangedSubview(fieldStackView3)
rootStack.addArrangedSubview(twoLineStack)
// fieldAndStepperStack needs width constrained to its superview (the twoLineStack) when
// in portrait orientation
// setting the priority to 999 prevents "nested stackView" constraint breaks
fieldAndStepperStackWidthConstraint = fieldAndStepperStack.widthAnchor.constraint(equalTo: twoLineStack.widthAnchor, multiplier: 1.0)
fieldAndStepperStackWidthConstraint.priority = UILayoutPriority(rawValue: 999)
// constrain fieldView3 width to fieldView2 width to keep them the same size
NSLayoutConstraint.activate([
fieldStackView3.widthAnchor.constraint(equalTo: fieldStackView2.widthAnchor, multiplier: 1.0)
])
rootStack.addArrangedSubview(fieldStackView4)
override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.verticalSizeClass == .regular
fieldAndStepperStackWidthConstraint.isActive = true
twoLineStack.axis = .vertical
twoLineStack.spacing = verticalSpacing
else if traitCollection.verticalSizeClass == .compact
fieldAndStepperStackWidthConstraint.isActive = false
twoLineStack.axis = .horizontal
twoLineStack.spacing = horizontalSpacing
else
print("Unexpected")
结果:
【讨论】:
在阅读了许多文章和反复试验之后,您发布的笔记简洁地汇集了我看到或怀疑的关键问题。你的例子效果很好。谢谢。【参考方案2】:当 UIView
添加到 UIStackView
时,stackView 将根据分配给 stackView 的属性(axis
、alignment
、distribution
、spacing
)为该视图分配约束。正如@DonMag 所提到的,您正在为aggregateStack
视图中的textField
添加一个约束。 aggregateStack
将根据其属性添加自己的约束。通过删除该约束和激活/停用代码,约束冲突消失。
我使用您的代码创建了一个小示例,并向 stackViews 添加了一些背景视图,以便您可以更轻松地查看更改各种属性时发生的情况。只是为了说明,我将rootStackView
固定在视图控制器视图的边缘,以便它可见。
import UIKit
class StackViewController: UIViewController
var rootStack: UIStackView!
var aggregateStack: UIStackView!
var field1: UITextField!
var field2: UITextField!
var f1f2TrailTrail: NSLayoutConstraint!
private lazy var backgroundView: UIView =
let view = UIView()
view.backgroundColor = .purple
view.layer.cornerRadius = 10.0
return view
()
private lazy var otherBackgroundView: UIView =
let view = UIView()
view.backgroundColor = .green
view.layer.cornerRadius = 10.0
return view
()
override func viewDidLoad()
super.viewDidLoad()
view.backgroundColor = .white
createIntializeViews()
createInitializeAddStacks()
private func createIntializeViews()
field1 = UITextField()
field1.backgroundColor = .orange
field2 = UITextField()
field2.backgroundColor = .blue
field1.text = "test 1"
field2.text = "test 2"
private func createInitializeAddStacks()
rootStack = UIStackView()
rootStack.alignment = .center
rootStack.distribution = .fillProportionally
pinBackground(backgroundView, to: rootStack)
aggregateStack = UIStackView()
aggregateStack.alignment = .center
aggregateStack.distribution = .fillProportionally
pinBackground(otherBackgroundView, to: aggregateStack)
// If I comment out the following, there are no constraint conflicts
aggregateStack.spacing = 5
field1.translatesAutoresizingMaskIntoConstraints = false
field2.translatesAutoresizingMaskIntoConstraints = false
aggregateStack.addArrangedSubview(field1)
aggregateStack.addArrangedSubview(field2)
rootStack.addArrangedSubview(aggregateStack)
view.addSubview(rootStack)
rootStack.translatesAutoresizingMaskIntoConstraints = false
/**
* pin the root stackview to the edges of the view controller, just so we can see
* it's behavior
*/
NSLayoutConstraint.activate([
rootStack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant:16),
rootStack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant:-16),
rootStack.topAnchor.constraint(equalTo: view.topAnchor, constant:32),
rootStack.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant:-32),
])
/**
* Inserts a UIView into the UIStackView's hierarchy, but not as part of the arranged subviews
* see https://useyourloaf.com/blog/stack-view-background-color/
*/
private func pinBackground(_ view: UIView, to stackView: UIStackView)
view.translatesAutoresizingMaskIntoConstraints = false
stackView.insertSubview(view, at: 0)
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: stackView.leadingAnchor),
view.trailingAnchor.constraint(equalTo: stackView.trailingAnchor),
view.topAnchor.constraint(equalTo: stackView.topAnchor),
view.bottomAnchor.constraint(equalTo: stackView.bottomAnchor)
])
override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
super.traitCollectionDidChange(previousTraitCollection)
switch traitCollection.verticalSizeClass
case .regular:
aggregateStack.axis = .vertical
case .compact:
aggregateStack.axis = .horizontal
case .unspecified:
print("Unexpected")
【讨论】:
我有后沿对齐控件,因为当我有一个每行有多个控件的表单时,我不知道有其他方法可以对齐边缘。 让我吃惊的是,当我第一次加载视图时,怎么没有约束冲突?然后我旋转设备并且没有约束冲突。但是将设备返回到初始的非垂直约束方向则显示约束冲突。在我看来,stackview 和 nonstackview 约束的应用变得复杂。如果不考虑我将stackview轴更改为垂直,就好像系统的约束检查代码一样。 wtfautolayout 输出表明旋转后存在挥之不去的水平约束。 可以肯定的是,我几乎总是假设我的某个假设或理解是错误的。我确实相信有某种方式我不会考虑需要更改的堆栈视图。是我应该使用堆栈视图或视图约束而不是混合两者吗? 我喜欢UIStackView
,但是当你开始嵌套它们时它们会变得很棘手。正如我所提到的,它们添加了自己的约束,这些约束可能与您添加的任何约束相冲突。您可能需要向视图添加约束以设置其大小,例如,如果 UIView
没有内在内容大小(如 UITextFields
或 UILabel
做),UIStackView
使用它来应用诸如fillEqually
。我尝试让UIStackView
完成大部分布局工作。
关于“当你开始嵌套它们时会变得很棘手”。我同意。看来我的表格很简单。然而,嵌套堆栈视图并没有约束冲突已被证明有些劳动密集型。我希望找到一些我可以应用的模式,以便更有效地生成表单。以上是关于当 trait 集合发生变化时,就会出现约束冲突,就好像 stackview 轴没有改变一样的主要内容,如果未能解决你的问题,请参考以下文章
当设备方向发生变化时,UIViewController 不会更新视图的约束
当 UINavigation 的 Translucent 设置为 false 时,视图约束发生变化
当集合视图大小发生变化时,我需要帮助为 UICollectionView 单元格大小设置动画