嵌套的UIStackViews破坏约束
我有一个复杂的视图层次结构,内置在Interface Builder中,嵌套的UIStackViews。 每当我隐藏一些内在的堆栈视图时,我会得到“不可满足的约束”通知。 我已经追查到这一点:
( "<NSLayoutConstraint:0x1396632d0 'UISV-canvas-connection' UIStackView:0x1392c5020.top == UILabel:0x13960cd30'Also available on iBooks'.top>", "<NSLayoutConstraint:0x139663470 'UISV-canvas-connection' V:[UIButton:0x139554f80]-(0)-| (Names: '|':UIStackView:0x1392c5020 )>", "<NSLayoutConstraint:0x139552350 'UISV-hiding' V:[UIStackView:0x1392c5020(0)]>", "<NSLayoutConstraint:0x139663890 'UISV-spacing' V:[UILabel:0x13960cd30'Also available on iBooks']-(8)-[UIButton:0x139554f80]>" )
具体来说, UISV-spacing
约束:当隐藏一个UIStackView的高约束得到一个0常量,但似乎与内部堆栈视图的间距约束冲突:它需要8点之间我的标签和button,这是不可调和的隐藏约束和所以约束崩溃。
有没有解决的办法? 我已经尝试recursion隐藏隐藏的堆栈视图的所有内部StackViews,但这会导致内容漂浮在屏幕之外的奇怪的animation,并导致严重的FPS丢弃引导,而仍然没有解决问题。
理想情况下,我们可以将UISV-spacing
约束的优先级设置为较低的值,但似乎没有任何方法可以做到这一点。 🙂
我已经成功地设置嵌套堆栈视图的spacing
属性为0之前隐藏,并恢复到正确的值后,使其再次可见。
我认为这样做嵌套堆栈视图recursion地工作。 您可以将spacing
属性的原始值存储在字典中,然后再恢复。
我的项目只有一个级别的嵌套,所以我不确定这是否会导致FPS问题。 只要你不间隔的变化animation,我不认为这会造成太大的打击。
这是隐藏嵌套堆栈视图的已知问题。
这个问题基本上有3个解决scheme:
- 将间距更改为0,但您需要记住以前的间距值。
- 调用
innerStackView.removeFromSuperview()
,但是你需要记住在哪里插入堆栈视图。 - 将堆栈视图封装在至less有999个约束的UIView中。 例如top @ 1000,@ 1000,trailing @ 1000,bottom @ 999。
第三个选项是我认为最好的。 有关此问题的更多信息,为什么会发生,不同的解决scheme以及如何实现解决scheme3,请参阅我对类似问题的回答 。
我遇到了与UISV隐藏类似的问题。 对我来说,解决的办法是把我自己的约束的优先级从Required(1000)减less到less于这个的数量。 当添加UISV隐藏约束时,它们优先,约束不再冲突。
所以,你有这个:
而问题是,当你第一次折叠内层堆栈时,会出现自动布局错误:
2017-07-02 15:40:02.377297-0500 nestedStackViews[17331:1727436] [LayoutConstraints] Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. ( "<NSLayoutConstraint:0x62800008ce90 'UISV-canvas-connection' UIStackView:0x7fa57a70fce0.top == UILabel:0x7fa57a70ffb0'Top Label of Inner Stack'.top (active)>", "<NSLayoutConstraint:0x62800008cf30 'UISV-canvas-connection' V:[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...']-(0)-| (active, names: '|':UIStackView:0x7fa57a70fce0 )>", "<NSLayoutConstraint:0x62000008bc70 'UISV-hiding' UIStackView:0x7fa57a70fce0.height == 0 (active)>", "<NSLayoutConstraint:0x62800008cf80 'UISV-spacing' V:[UILabel:0x7fa57a70ffb0'Top Label of Inner Stack']-(8)-[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...'] (active)>" ) Will attempt to recover by breaking constraint <NSLayoutConstraint:0x62800008cf80 'UISV-spacing' V:[UILabel:0x7fa57a70ffb0'Top Label of Inner Stack']-(8)-[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...'] (active)> Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
正如你所指出的那样,问题是外部堆栈视图对内部堆栈视图应用height = 0约束。 这与内部堆栈视图在其子视图之间应用的8点填充约束相冲突。 两个约束条件都不能同时满足。
外层堆栈视图使用这个height = 0约束,我相信,因为它看起来更好的animation,而不是让内部视图隐藏不先收缩。
这里有一个简单的修复:在一个普通的UIView
包装内部堆栈视图,并隐藏该包装。 我会演示。
以下是上述破碎版本的场景轮廓:
要解决该问题,请select内部堆栈视图。 从菜单栏中select编辑器>embedded>查看:
Interface Builder在包装器视图上创build宽度约束,所以删除宽度约束:
接下来,在包装的所有四个边和内部堆栈视图之间创build约束:
此时,布局在运行时实际上是正确的,但Interface Builder将其错误地绘制。 你可以通过设置内层堆栈的子项的垂直拥抱优先级来修复它。 我把它们设置为800:
在这一点上,我们并没有真正确定不可约束的约束问题。 为此,find您刚刚创build的底部约束,并将其优先级设置为小于所需的。 我们把它改成800:
最后,你大概在你的视图控制器连接到内部堆栈视图有一个sockets,因为你正在改变它的hidden
属性。 更改该出口连接到包装视图,而不是内部堆栈视图。 如果你的sockets的types是UIStackView
,你需要把它改成UIView
。 我已经是UIView
types的,所以我只是在故事板上重新连接它:
现在,当您切换包装视图的hidden
属性时,堆栈视图将显示为折叠状态,并且没有不可满足的约束警告。 它看起来几乎相同,所以我不会打扰发布另一个应用程序的GIF运行。
你可以在这个github仓库中find我的testing项目。
这里是Senseful的build议#3的使用SnapKit约束写成Swift 3类的实现。 我也尝试覆盖的属性,但从来没有得到它的工作没有警告,所以我会坚持包装UIStackView:
class NestableStackView: UIView { private var actualStackView = UIStackView() override init(frame: CGRect) { super.init(frame: frame); addSubview(actualStackView); actualStackView.snp.makeConstraints { (make) in // Lower edges priority to allow hiding when spacing > 0 make.edges.equalToSuperview().priority(999); } } convenience init() { self.init(frame: CGRect.zero); } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func addArrangedSubview(_ view: UIView) { actualStackView.addArrangedSubview(view); } func removeArrangedSubview(_ view: UIView) { actualStackView.removeArrangedSubview(view); } var axis: UILayoutConstraintAxis { get { return actualStackView.axis; } set { actualStackView.axis = newValue; } } open var distribution: UIStackViewDistribution { get { return actualStackView.distribution; } set { actualStackView.distribution = newValue; } } var alignment: UIStackViewAlignment { get { return actualStackView.alignment; } set { actualStackView.alignment = newValue; } } var spacing: CGFloat { get { return actualStackView.spacing; } set { actualStackView.spacing = newValue; } } }
另一种方法
尽量避免嵌套的UIStackViews。 我爱他们,并与他们几乎所有的东西。 但是,正如我认识到他们偷偷添加约束条件,我只尝试在最高级别使用它们,并尽可能使用非嵌套。 这样我可以指定第二高的优先级.defaultHigh
到间距约束,这解决了我的警告。
这个优先级足以防止大多数布局问题。
当然你需要指定一些更多的约束条件,但是这样你可以完全控制它们,并使你的视图布局明确。