"squished" 个隐藏视图上的 UIStackView "Unable to simultaneously satisfy constraints"

UIStackView "Unable to simultaneously satisfy constraints" on "squished" hidden views

当我的 UIStackView "rows" 被压缩时,它们会抛出 AutoLayout 警告。但是,它们显示正常,除了这些日志之外没有其他错误:

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. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) (

所以,我还不确定如何解决这个问题,但它似乎并没有破坏任何东西,除了令人讨厌之外。

有谁知道怎么解决吗?有趣的是,布局约束经常被标记为 'UISV-hiding',表明在这种情况下它可能应该忽略子视图或其他东西的高度最小值?

大多数时候,可以通过降低约束优先级以消除冲突来解决此错误。

您可能在使用特定尺寸 class(例如:wCompact hRegular)时创建了一个约束,然后当您切换到另一个尺寸 class(例如:wAny hAny ).检查 UI 个不同大小 class 对象的约束,看看约束是否存在异常。你应该看到红线表示碰撞约束。在我得到 10 个声望点之前我不能放照片抱歉:/

此错误与 UIStackView 无关。当您遇到具有相同优先级的冲突约束时,就会发生这种情况。例如,如果您有一个约束声明视图的宽度为 100,而您同时有另一个约束声明视图的宽度为其容器的 25%。显然有两个相互冲突的约束。解决方案是删除其中一个。

之所以会出现此问题,是因为将 UIStackView 中的子视图设置为隐藏时,它会首先将其高度限制为零,以便将其动画化。

我收到以下错误:

2015-10-01 11:45:13.732 <redacted>[64455:6368084] 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. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSLayoutConstraint:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-|   (Names: '|':UIView:0x7f7f5be69d30 )>",
    "<NSLayoutConstraint:0x7f7f5be508d0 V:|-(8)-[UISegmentedControl:0x7f7f5bec4180]   (Names: '|':UIView:0x7f7f5be69d30 )>",
    "<NSLayoutConstraint:0x7f7f5bdfbda0 'UISV-hiding' V:[UIView:0x7f7f5be69d30(0)]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-|   (Names: '|':UIView:0x7f7f5be69d30 )>

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.

我想做的是在我的 UIStackView 中放置一个 UIView,其中包含一个 UISegmentedControl 在每条边上插入 8pts。

当我将它设置为隐藏时,它会尝试将容器视图限制为零高度,但因为我有一组从上到下的限制,所以发生了冲突。

为了解决这个问题,我将我的 8pt 顶部和底部约束优先级从 1000 更改为 999,以便 UISV-hiding 约束可以在需要时优先考虑。

我想一次隐藏整个 UIStackView,但我遇到了与 OP 相同的错误,这为我修复了它:

for(UIView *currentView in self.arrangedSubviews){
    for(NSLayoutConstraint *currentConstraint in currentView.constraints){
        [currentConstraint setPriority:999];
    }
}

我遇到了一个不容易解决的类似问题。在我的例子中,我有一个嵌入在堆栈视图中的堆栈视图。内部 UIStackView 有两个标签和指定的非零间距。

当您调用 addArrangedSubview() 时,它会自动创建类似于以下内容的约束:

V:|[innerStackView]|              | = outerStackView

  V:|[label1]-(2)-[label2]|       | = innerStackView

现在,当您尝试隐藏 innerStackView 时,您会收到不明确的约束警告。

要理解为什么,我们先来看看为什么当innerStackView.spacing等于0不会发生。当你调用 innerStackView.hidden = true 时,@liamnichols 是正确的...... outerStackView 会神奇地拦截这个调用,并创建一个 0 高度 UISV-hiding约束优先级 1000(必需)。据推测,这是为了在 UIView.animationWithDuration() 块中调用隐藏代码时允许堆栈视图中的元素动画化。不幸的是,似乎没有办法阻止添加此约束。不过,您不会收到 "Unable to simultaneously satisfy constraints" (USSC) 警告,因为会发生以下情况:

  1. label1 的高度设置为 0
  2. 两个标签之间的间距已经定义为0
  3. label2 的高度设置为 0
  4. innerStackView 的高度设置为 0

很明显可以满足这 4 个约束条件。堆栈视图只是将所有内容都压缩到一个 0 高度的像素中。

现在回到越野车示例,如果我们将 spacing 设置为 2,我们现在有这些约束:

  1. label1 的高度设置为 0
  2. 两个标签之间的间距由堆栈视图自动创建为 2 像素高,优先级为 1000。
  3. label2 的高度设置为 0
  4. innerStackView 的高度设置为 0

堆栈视图不能同时为 0 像素高且其内容为 2 像素高。无法满足约束。

注意:您可以通过一个更简单的示例来了解此行为。只需将 UIView 添加到堆栈视图作为排列的子视图。然后在优先级为 1000 的 UIView 上设置高度约束。现在尝试调用隐藏。

注意:无论出于何种原因,这只发生在我的堆栈视图是 UICollectionViewCell 或 UITableViewCell 的子视图时。但是,您仍然可以在隐藏内部堆栈视图后通过在下一个 运行 循环中调用 innerStackView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) 来在单元格外重现此行为。

注意:即使您尝试在 UIView.performWithoutAnimations 中执行代码,堆栈视图仍会添加 0 高度约束,这将导致 USSC 警告。


这个问题至少有3种解法:

  1. 在堆栈视图中隐藏任何元素之前,检查它是否是堆栈视图,如果是,将 spacing 更改为 0。 这很烦人,因为你每当您再次显示内容时,都需要反转过程(并记住原始间距)。
  2. 不要在堆栈视图中隐藏元素,而是调用 removeFromSuperview. 这更烦人,因为当你反转这个过程时,你需要记住 where 插入删除的项目。您可以通过仅调用 removeArrangedSubview 然后隐藏来进行优化,但是仍然需要做很多簿记工作。
  3. 在 UIView 中包装嵌套堆栈视图(具有非零 spacing)。将至少一个约束指定为非必需优先级(999 或以下)。 这是最佳解决方案,因为您不必进行任何簿记。在我的示例中,我在堆栈视图和包装视图之间的 1000 处创建了顶部、前导和尾随约束,然后从堆栈视图底部到包装视图创建了 999 约束。这样,当外部堆栈视图创建零高度约束时,999 约束将被打破,您不会看到 USSC 警告。 (注意:这个和的解决方法类似)

总而言之,您出现这种行为的原因是:

  1. 当您将托管子视图添加到堆栈视图时,Apple 会自动为您创建 1000 个优先级约束。
  2. 当您隐藏堆栈视图的子视图时,Apple 会自动为您创建 0 高度约束。

如果 Apple (1) 允许您指定约束的优先级(尤其是间隔符),或者 (2) 允许您选择退出自动 UISV-hiding约束,这个问题就迎刃而解了

根据@Senseful 的回答,这里有一个 UIStackView 扩展,用于将堆栈视图包装在视图中并应用他或她推荐的约束:

/// wraps in a `UIView` to prevent autolayout warnings when a stack view with spacing is placed inside another stack view whose height might be zero (usually due to `hidden` being `true`).
/// See 
func wrapped() -> UIView {
    let wrapper = UIView()
    translatesAutoresizingMaskIntoConstraints = false
    wrapper.addSubview(self)

    for attribute in [NSLayoutAttribute.Top, .Left, .Right, .Bottom] {
        let constraint = NSLayoutConstraint(item: self,
                                            attribute: attribute,
                                            relatedBy: .Equal,
                                            toItem: wrapper,
                                            attribute: attribute,
                                            multiplier: 1,
                                            constant: 0)
        if attribute == .Bottom { constraint.priority = 999 }
        wrapper.addConstraint(constraint)
    }
    return wrapper
}

不要添加 stackView,而是使用 stackView.wrapped()

NOP 与 [mySubView removeFromSuperview]。 我希望它可以帮助某人:)

当您将视图设置为隐藏时,UIStackview 将尝试将其动画化。如果你想要那种效果,你需要为约束设置正确的优先级,这样它们就不会发生冲突(正如上面许多人所建议的那样)。

但是,如果您不关心动画(也许您将其隐藏在 ViewDidLoad 中),那么您可以简单地 removeFromSuperview 这将具有相同的效果,但没有任何约束问题,因为这些将与视图一起删除。

首先,正如其他人所建议的那样,确保您可以控制的约束,即不是 UIStackView 固有的约束设置为优先级 999,以便在隐藏视图时可以覆盖它们。

如果您仍然遇到此问题,则问题可能是由于隐藏的 StackView 中的间距所致。 我的解决方案是添加一个 UIView 作为间隔并将 UIStackView 间距设置为零。 然后设置 View.height 或 View.width 约束(取决于垂直或水平堆栈) 到 StackView 的间距。

然后调整新添加的视图的内容拥抱和内容压缩阻力优先级。您可能还必须更改父级 StackView 的分布。

以上所有都可以在界面生成器中完成。您可能还必须以编程方式 hide/unhide 一些新添加的视图,这样您就没有不需要的间距。

我有一排有高度限制的按钮。当一个按钮被隐藏时会发生这种情况。将该按钮高度限制的优先级设置为 999 已解决该问题。

我最近在隐藏 UIStackView 时遇到自动布局错误。我没有在 UIViews 中做一堆簿记和包装堆栈,而是选择为我的 parentStackView 创建一个出口,并为我想要 hide/unhide 的 children 创建一个出口。

@IBOutlet weak var parentStackView: UIStackView!
@IBOutlet var stackViewNumber1: UIStackView!
@IBOutlet var stackViewNumber2: UIStackView!

在故事板中,这是我的 parent堆栈的样子:

它有 4 个 children,每个 children 里面都有一堆堆栈视图。当您隐藏堆栈视图时,如果它有 UI 个元素也是堆栈视图,您将看到一连串的自动布局错误。我没有隐藏,而是选择删除它们。

在我的示例中,parentStackViews 包含 4 个元素的数组:Top Stack View、StackViewNumber1、Stack View Number 2 和 Stop Button。它们在 arrangedSubviews 中的索引分别为 0、1、2 和 3。当我想隐藏一个时,我只需将它从 parentStackView's arrangedSubviews 数组中删除即可。由于它并不弱,它会留在内存中,您可以稍后将它放回您想要的索引处。我没有重新初始化它,所以它只是挂起直到需要它,但不会膨胀内存。

基本上,你可以...

1) 将 parent 堆栈的 IBOutlets 和您想要 hide/unhide 的 children 拖到情节提要中。

2) 当你想隐藏它们时,从parentStackView's arrangedSubviews数组中移除你想隐藏的堆栈。

3) 用 UIView.animateWithDuration.

调用 self.view.layoutIfNeeded()

注意最后两个 stackView 不是 weak。当您取消隐藏它们时,您需要保留它们。

假设我想隐藏 stackViewNumber2:

parentStackView.removeArrangedSubview(stackViewNumber2)
stackViewNumber2.removeFromSuperview()

然后制作动画:

UIView.animate(withDuration: 0.25,
               delay: 0,
               usingSpringWithDamping: 2.0,
               initialSpringVelocity: 10.0,
               options: [.curveEaseOut],
               animations: {
                self.view.layoutIfNeeded()
},
               completion: nil)

如果您以后想 "unhide" 一个 stackViewNumber2,您可以将它插入所需的 parentStackView arrangedSubViews 索引并动画更新。

parentStackView.removeArrangedSubview(stackViewNumber1)
stackViewNumber1.removeFromSuperview()
parentStackView.insertArrangedSubview(stackViewNumber2, at: 1)

// Then animate it
UIView.animate(withDuration: 0.25,
               delay: 0,
               usingSpringWithDamping: 2.0,
               initialSpringVelocity: 10.0,
               options: [.curveEaseOut],
               animations: {
                self.view.layoutIfNeeded()
},
               completion: nil)

我发现这比记录约束、摆弄优先级等要容易得多

如果你有一些你想默认隐藏的东西,你可以把它放在故事板上,然后在 viewDidLoad 中删除它,然后使用 view.layoutIfNeeded().

在没有动画的情况下更新

我在使用嵌入式堆栈视图时遇到了同样的错误,尽管在运行时一切正常。

我通过在隐藏父堆栈视图之前先隐藏所有子堆栈视图(设置 isHidden = true)来解决约束错误。

这样做并没有删除子排列视图的所有复杂性,维护索引以供需要时将它们添加回来。

希望这对您有所帮助。

Senseful 对上述问题的根源提供了很好的答案,所以我将直接进入解决方案。

您需要做的就是将所有 stackView 约束优先级设置为低于 1000(999 即可)。例如,如果 stackView 被左、右、上、下约束到它的父视图,那么所有 4 个约束的优先级都应该低于 1000。