UIStackView“无法同时满足约束”对“压缩”的隐藏视图


95

当我的UIStackView“行”被压缩时,它们会AutoLayout发出警告。但是,它们显示正常,除了这些日志记录之外,没有其他问题:

无法同时满足约束条件。以下列表中的约束中至少有一个是您不想要的约束。尝试以下操作:(1)查看每个约束,并尝试找出不期望的约束;(2)查找添加了一个或多个不必要约束的代码并进行修复。(注意:如果您看NSAutoresizingMaskLayoutConstraints不懂,请参阅该UIView属性的文档translatesAutoresizingMaskIntoConstraints)(

因此,我不确定如何解决此问题,但是除了烦人之外,它似乎并没有破坏任何东西。

有人知道如何解决吗?有趣的是,布局约束经常用“ UISV隐藏”标记,表明在这种情况下也许应该忽略子视图或某些东西的高度最小值?


1
这似乎已在iOS11中修复,在此处未收到任何警告
陷阱

Answers:


204

之所以会出现此问题,是因为在将子视图从内部设置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.

我想做的是UIView在我的每个边缘上放置一个UIStackView包含8个点的UISegmentedControl插图的内部。

当我将其设置为“隐藏”时,它将尝试将容器视图限制为零高度,但是由于我从上到下都有一组约束,因此发生了冲突。

为了解决该问题,我将我的8pt顶部底部约束优先级从1000更改为999,这样,UISV-hiding如果需要的话,约束可以具有优先级。


应该注意的是,如果您仅对这些元素设置了高度或宽度限制,并降低了它们的优先级,那么它将无法正常工作。您需要删除高度/宽度并添加顶部的尾随底部,然后将其优先级设置为较低,然后才能使用
bolnad

4
更改优先级对我也很有效。同样,删除从未使用的大小类中意外复制的任何多余(变暗)约束。重要提示:为了更轻松地调试这些问题,请在每个约束上设置一个IDENTIFER字符串。然后,您可以在调试消息中看到哪个约束是顽皮的。
Womble

3
就我而言,我只需要降低高度的优先级即可。
pixelfreak

IDENTIFIER提示很棒!我一直想知道如何在调试消息名称中给出约束,我一直想在视图中添加一些东西,而不是约束本身。谢谢@Womble!
瑞安(Ryan)

谢谢!从1000到999的优先级解决了问题。Xcode:8.3.3版(8E3004b)
Michael Garito

51

我有一个类似的问题,很难解决。就我而言,我在堆栈视图中嵌入了一个堆栈视图。内部的UIStackView具有两个标签和指定的非零间距。

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

V:|[innerStackView]|              | = outerStackView

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

现在,当您尝试隐藏innerStackView时,您将得到一个模糊的约束警告。

要了解原因,首先让我们看看为什么等于时发生这种情况。当您调用时,@ liamnichols是正确的... 将会神奇地拦截此调用,并创建优先级为1000(必需)的UISV隐藏隐藏高度。假定这是为了使堆栈视图中的元素动画化,以防万一您在块内调用隐藏代码。不幸的是,似乎没有一种方法可以防止添加此约束。但是,由于发生以下情况,您不会收到“无法同时满足约束”(USSC)警告:innerStackView.spacing0innerStackView.hidden = trueouterStackView0UIView.animationWithDuration()

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

很明显,可以满足这四个约束。堆栈视图只是将所有内容拖到高度为0的像素中。

现在回到有问题的示例,如果将设置spacing2,则现在具有以下约束:

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

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

注意:您可以通过一个更简单的示例看到此行为。只需将UIView作为已安排的子视图添加到堆栈视图即可。然后在具有1000优先级的UIView上设置高度限制。现在尝试在其上调用hide。

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

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


至少有3个解决方案可以解决此问题:

  1. 在堆栈视图中隐藏任何元素之前,请检查它是否为堆栈视图,如果是,则将spacing其更改为0。这很烦人,因为每当您再次显示内容时,您都需要反转该过程(并记住原始间距)。
  2. 而不是在堆栈视图中隐藏元素,请调用removeFromSuperview这更令人烦恼,因为当您逆向执行此过程时,您需要记住在何处插入已删除项目。您可以通过仅调用removeArrangedSubview然后隐藏来进行优化,但是仍有许多簿记工作需要完成。
  3. 将嵌套的堆栈视图(具有非零值spacing)包装在UIView中。将至少一个约束条件指定为不需要的优先级(999或更低)。这是最好的解决方案,因为您无需进行任何簿记。在我的示例中,我在堆栈视图和包装器视图之间在1000处创建了顶部约束,前导约束和尾随约束,然后从堆栈视图的底部到包装器视图创建了999个约束。这样,当外部堆栈视图创建零高度约束时,999约束将被破坏,并且您不会看到USSC警告。(注意:这类似于将UICollectionViewCell子类的contentView.translatesAutoResizingMaskToConstraints设置为false的解决方案)

总而言之,您得到此行为的原因是:

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

如果Apple(1)允许您指定约束的优先级(尤其是间隔),或者(2)允许您选择退出自动UISV隐藏的约束,则可以轻松解决此问题。


6
感谢您提供的详尽而有用的解释。但是,这肯定看起来像是苹果在堆栈视图方面的一个错误。基本上,它们的“隐藏”功能与它们的“间隔”功能不兼容。从那以后,他们是否有任何想法解决了这个问题,或者添加了一些功能来防止黑客利用额外的包含视图来进行破解?(再次,潜在解决方案的大量细分,并且确实接受了#3的优雅)
Marchy

1
是否需要将每个需要包裹的UIStackView孩子UIStackView包裹在一个UIView或只是您想要隐藏/隐藏的孩子中?
阿德里安

1
对于苹果公司而言,这感觉真是一个糟糕的监督。尤其是因为围绕使用的警告和错误UIStackView往往是晦涩难懂的。
bompf

这是一个救命稻草。我遇到的问题是,每次重用单元格时,添加到UITableViewCell的UIStackViews都会导致AutoLayout错误日志垃圾邮件。将stackView嵌入到其底部锚点约束优先级设置为低的UIView中解决了该问题。确实会导致视图调试器将stackView的元素显示为不明确的高度,但它会在应用程序中正确显示,而不会出现错误日志垃圾邮件。谢谢。
Womble

6

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


你什么意思?约束条件是所有被堆叠视图中的相对....
本公会

抱歉,我不明白您的问题,我的英语是这样,但是我认为约束相对于与其关联的视图是相对的,如果视图被隐藏,则约束变为无效。我不确定这是否是您的疑问,但希望我能提供帮助。
卢西亚诺·阿尔梅达

它似乎是从压缩东西或显示/隐藏过程中产生的。在这种情况下,它变得部分可见。—也许有必要实际消除所有最小的垂直常数,因为它可以压成0高度?
本协会2015年

2

将视图设置为隐藏时, UIStackview它将尝试对其进行动画处理。如果需要这种效果,则需要为约束设置正确的优先级,以使它们不会发生冲突(如上文所述)。

但是,如果您不关心动画(也许您将其隐藏在ViewDidLoad中),则可以简单地实现动画removeFromSuperview效果,但是不会产生任何约束问题,因为这些约束将随视图一起移除。


1

根据@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 http://stackoverflow.com/questions/32428210
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()


1

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

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

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

以上所有操作都可以在Interface Builder中完成。您可能还必须以编程方式隐藏/取消隐藏一些新添加的视图,以免出现不必要的间距。


1

我最近在隐藏时遇到了自动布局错误UIStackViewUIViews我没有创建一堆书本和包装纸堆,而是选择为自己的parentStackView商店和想要隐藏/取消隐藏的孩子创建商店。

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

在情节提要中,这是我的parentStack的样子:

在此处输入图片说明

它有4个孩子,每个孩子里面都有很多堆栈视图。隐藏堆栈视图时,如果UI元素也是堆栈视图,则会看到一系列自动布局错误。我选择隐藏而不是隐藏。

在我的示例中,parentStackViews包含4个元素的数组:顶部堆栈视图,StackViewNumber1,堆栈视图编号2和停止按钮。它们的索引arrangedSubviews分别为0、1、2和3。当我想隐藏一个时,只需将其从parentStackView's arrangedSubviews数组中删除即可。由于它并不弱,它会在内存中徘徊,您可以稍后将其放回所需的索引。我没有重新初始化它,所以它只是一直挂到需要它时才使用,但不会使内存膨胀。

所以基本上,您可以...

1)将IBOutlets拖到您的父堆栈和要隐藏/取消隐藏的故事板上的子代。

2)当您要隐藏它们时,从parentStackView's arrangedSubviews阵列中删除要隐藏的堆栈。

3)呼叫self.view.layoutIfNeeded()使用UIView.animateWithDuration

注意最后两个stackViews不是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)

如果以后要“取消隐藏” 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()


1

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

我先隐藏所有子堆栈视图(设置isHidden = true),然后再隐藏父堆栈视图,从而解决了约束错误。

这样做并没有删除子视图的全部复杂性,而是在需要重新添加子视图时维护索引。

希望这可以帮助。


1

有道理为上述问题的根源提供了一个很好的答案,因此我将直接解决。

您需要做的就是将所有stackView约束的优先级设置为低于1000(999可以完成工作)。例如,如果将stackView限制为其超级视图的左,右,顶部和底部,则所有4个约束的优先级均应低于1000。


0

您可能在使用某个尺寸等级(例如:wCompact hRegular)时创建了约束,然后在切换到另一个尺寸等级(例如:wAny hAny)时创建了重复项。检查不同大小类中的UI对象的约束,并查看约束是否存在异常。您应该看到红线表示冲突约束。在得到10个信誉点之前,我无法贴图片://


哦好的。但是当我遇到描述drive.google.com/file/d/0B-mn7bZcNqJMVkt0OXVLVVdnNTA/
FM

是的,在接口构建器中切换大小类绝对不会看到任何红色。我只使用“任何”尺寸。
本协会2015年

0

我想一次隐藏整个UIStackView,但是我遇到了与OP相同的错误,这对我来说是固定的:

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

这对我不起作用,因为当您将所需的约束(priority = 1000)更改为不需要()时,自动布局引擎会抱怨priority <= 999
有意义的2016年

0

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


-2

此错误与UIStackView没有关系。当您具有相同优先级的冲突约束时,就会发生这种情况。例如,如果有一个约束说明视图的宽度为100,而同时有另一个约束则说明视图的宽度为其容器的25%。显然有两个相互矛盾的约束。解决的办法是删除它们。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.