嵌套的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:

  1. 将间距更改为0,但您需要记住以前的间距值。
  2. 调用innerStackView.removeFromSuperview() ,但是你需要记住在哪里插入堆栈视图。
  3. 将堆栈视图封装在至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 。 我已经是UIViewtypes的,所以我只是在故事板上重新连接它:

改变出口

现在,当您切换包装视图的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到间距约束,这解决了我的警告。

这个优先级足以防止大多数布局问题。

当然你需要指定一些更多的约束条件,但是这样你可以完全控制它们,并使你的视图布局明确。