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

Posted

技术标签:

【中文标题】UIStackView“无法同时满足约束”“压扁”隐藏视图【英文标题】:UIStackView "Unable to simultaneously satisfy constraints" on "squished" hidden views 【发布时间】:2015-12-02 09:24:39 【问题描述】:

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

无法同时满足约束。 以下列表中的至少一个约束可能是您不想要的。试试这个:(1)查看每个约束并尝试找出您不期望的; (2) 找到添加了一个或多个不需要的约束的代码并修复它。 (注意:如果您看到不理解的NSAutoresizingMaskLayoutConstraints,请参阅UIView 属性translatesAutoresizingMaskIntoConstraints 的文档) (

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

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

【问题讨论】:

这似乎已在 ios11 中修复,此处未收到任何警告 【参考方案1】:

Senseful 已经为上述问题的根源提供了一个很好的答案,所以我将直接找到解决方案。

您需要做的就是将所有 stackView 约束优先级设置为低于 1000(999 将完成工作)。例如,如果 stackView 被限制在其父视图的左、右、上和下,那么所有 4 个约束的优先级都应该低于 1000。

【讨论】:

【参考方案2】:

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

【讨论】:

什么意思?约束在堆叠的视图中都是相对的...... 对不起,我不明白你的问题,我的英语是如此,但我认为约束是相对于它所关联的视图,如果视图被隐藏约束变得无效。我不确定这是否是你的疑问,但我希望我能提供帮助。 它似乎来自东西被压扁或正在显示/隐藏的过程中。在这种情况下,它变得部分可见。 — 可能有必要实际检查并消除任何最小垂直常数,因为它可以压缩到 0 高度?【参考方案3】:

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

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

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

希望这会有所帮助。

【讨论】:

【参考方案4】:

我最近在隐藏 UIStackView 时遇到了自动布局错误。我没有在UIViews 中做一堆簿记和包装堆栈,而是选择为我的parentStackView 创建一个插座,并为我想要隐藏/取消隐藏的孩子创建一个插座。

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

在故事板中,我的 parentStack 如下所示:

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

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

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

1) 将父堆栈的 IBOutlets 和要隐藏/取消隐藏的子堆栈拖到情节提要中。

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

3) 使用UIView.animateWithDuration 致电self.view.layoutIfNeeded()

注意最后两个 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() 在不使用动画的情况下进行更新。

【讨论】:

【参考方案5】:

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

【讨论】:

【参考方案6】:

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

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

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

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

【讨论】:

【参考方案7】:

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

但是,如果您不关心动画(也许您将其隐藏在 ViewDidLoad 中),那么您可以使用简单的removeFromSuperview,它会产生相同的效果,但不会出现任何约束问题,因为这些约束将与视图。

【讨论】:

【参考方案8】:

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

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

V:|[innerStackView]|              | = outerStackView

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

现在,当您尝试隐藏 innerStackView 时,您会收到一个模棱两可的约束警告。

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

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

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

现在回到错误的例子,如果我们将spacing 设置为2,我们现在有这些约束:

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

堆栈视图不能既是 0 像素高,又是其内容高 2 像素。无法满足约束。

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

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

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


这个问题至少有3个解决方案:

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

总之,你得到这种行为的原因是:

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

如果 Apple (1) 允许您指定约束(尤其是间隔)的优先级,或者 (2) 允许您选择退出自动 UISV-hiding 约束,这个问题很容易解决。

【讨论】:

感谢您提供的超级彻底和有用的解释。然而,这绝对看起来像是 Apple 的堆栈视图错误。基本上他们的“隐藏”功能与他们的“间距”功能不兼容。从那以后他们有什么想法解决了这个问题,或者添加了一些功能来防止使用额外的包含视图进行黑客攻击? (再次,对潜在解决方案进行了大分解,并且同意#3 的优雅) UIStackView 的每个UIStackView 子节点是否都需要包裹在 UIView 中,或者只是您想要隐藏/取消隐藏的那个? 这感觉像是苹果公司的一个非常糟糕的疏忽。尤其是因为与使用 UIStackView 相关的警告和错误往往晦涩难懂。 这是救命稻草。我遇到了一个问题,每次重用单元格时,添加到 UITableViewCell 的 UIStackViews 都会导致 AutoLayout 错误日志垃圾邮件。将 stackView 嵌入到 UIView 中,其底部锚约束优先级设置为低,解决了该问题。它确实会导致视图调试器将 stackView 的元素显示为具有不明确的高度,但它会在应用程序中正确显示,而不会出现错误日志垃圾邮件。谢谢。【参考方案9】:

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

【讨论】:

抱歉,什么是 NOP?【参考方案10】:

根据@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://***.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()

【讨论】:

【参考方案11】:

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

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

【讨论】:

这对我不起作用,因为当您将必需的约束 (priority = 1000) 更改为不需要的约束 (priority <= 999) 时,自动布局引擎会报错。【参考方案12】:

您会遇到此问题,因为当将 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 在每条边上插入8 个点。

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

为了解决这个问题,我将我的 8pt 上下约束优先级从 1000 更改为 999,以便 UISV-hiding 约束可以在需要时获得优先级。

【讨论】:

应该注意的是,如果你只是对这些有高度或宽度限制并降低它们的优先级,它就行不通了。您需要删除高度/宽度并添加顶部前导尾随底部,然后将它们的优先级设置为较低并且它可以工作 更改优先级也对我有用。此外,删除从未使用的大小类中意外复制的任何多余(变暗)约束。重要提示:要更轻松地调试这些问题,请在每个约束上设置一个 IDENTIFER 字符串。然后你可以在调试消息中看到哪个约束在调皮。 就我而言,我只需要降低高度的优先级就可以了。 那个标识符提示很棒!我一直想知道如何在调试消息名称中给出约束,我一直在寻找向视图添加一些东西而不是约束本身。谢谢@Womble! 谢谢!从 1000 到 999 的优先级成功了。Xcode:版本 8.3.3 (8E3004b)【参考方案13】:

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

【讨论】:

【参考方案14】:

您可能在使用某个尺寸类(例如:wCompact hRegular)时创建了一个约束,然后在切换到另一个尺寸类(例如:wAny hAny)时创建了一个副本。检查不同尺寸类的 UI 对象的约束,看看约束是否存在异常。您应该看到指示碰撞约束的红线。在我获得 10 点声望点之前我不能放图片抱歉:/

【讨论】:

哦,好的。但是当我遇到我描述的drive.google.com/file/d/0B-mn7bZcNqJMVkt0OXVLVVdnNTA/… 的情况时,我得到了那个错误 是的,通过在界面生成器中切换大小类,我绝对看不到任何红色。我只使用了“Any”尺寸。

以上是关于UIStackView“无法同时满足约束”“压扁”隐藏视图的主要内容,如果未能解决你的问题,请参考以下文章

以编程方式应用自动布局约束时无法同时满足约束

自动布局约束:无法同时满足约束

关闭:“无法同时满足约束”[重复]

iOS - AutoLayout'无法同时满足约束'

如何解决无法同时满足约束

UITableViewCell - 无法同时满足约束