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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32428210/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 07:32:14  来源:igfitidea点击:

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

iosautolayoutuikitios9uistackview

提问by Ben Guild

When my UIStackView "rows" are squished, they throw AutoLayoutwarnings. However, they display fine and nothing else is wrong besides these sorts of loggings:

当我的 UIStackView “行”被压扁时,它们会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 NSAutoresizingMaskLayoutConstraintsthat you don't understand, refer to the documentation for the UIViewproperty translatesAutoresizingMaskIntoConstraints) (

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

So, I'm not sure how to fix this yet, but it doesn't seem to break anything besides just being annoying.

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

Does anyone know how to solve it? Interestingly, the layout constraints are tagged quite often with 'UISV-hiding', indicating that perhaps it should ignore the height minimums for subviews or something in this instance?

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

回答by liamnichols

You get this issue because when setting a subview from within UIStackViewto hidden, it will first constrain its height to zero in order to animate it out.

您会遇到此问题,因为在将子视图从内部设置UIStackView为隐藏时,它会首先将其高度限制为零,以便将其设置为动画。

I was getting the following error:

我收到以下错误:

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.

What I was trying to do, was to place a UIViewwithin my UIStackViewthat contained a UISegmentedControlinset by 8pts on each edge.

我想要做的是UIView在我的内部放置一个UIStackView,其中UISegmentedControl每个边缘都包含一个8pts的插图。

When I set it to hidden, it would try to constrain the container view to a zero height but because i have a set of constraints from top to bottom, there was a conflict.

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

To resolve the issue, I changed my 8pt top an bottom constraints priority from 1000 to 999 so the UISV-hidingconstraint can then take priority if needed.

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

回答by Senseful

I was having a similar problem that wasn't easy to resolve. In my case, I had a stack view embedded in a stack view. The internal UIStackView had two labels and a non-zero spacing specified.

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

When you call addArrangedSubview() it will automatically create constraints similar to the following:

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

V:|[innerStackView]|              | = outerStackView

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

Now when you try to hide the innerStackView, you get an ambiguous constraints warning.

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

To understand why, let's first see why this doesn'thappen when innerStackView.spacingis equal to 0. When you call innerStackView.hidden = true, @liamnichols was correct... the outerStackViewwill magically intercept this call, and create a 0height UISV-hidingconstrain with priority 1000 (required). Presumably this is to allow elements in the stack view to be animated out of view in case your hiding code is called within a UIView.animationWithDuration()block. Unfortunately, there doesn't seem to be a way to prevent this constraint from being added. Nevertheless, you won't get an "Unable to simultaneously satisfy constraints" (USSC) warning, since the following happens:

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

  1. label1's height is set to 0
  2. the spacing between the two labels was already defined as 0
  3. label2's height is set to 0
  4. the innerStackView's height is set to 0
  1. label1 的高度设置为 0
  2. 两个标签之间的间距已经定义为 0
  3. label2 的高度设置为 0
  4. innerStackView 的高度设置为 0

It's clear to see that those 4 constraints can be satisfied. The stack view simply smooshes everything into a 0-height pixel.

很明显,可以满足这 4 个约束。堆栈视图简单地将所有内容都平滑为 0 高度像素。

Now going back to the buggy example, if we set the spacingto 2, we now have these constraints:

现在回到有问题的例子,如果我们将 设置spacing2,我们现在有这些约束:

  1. label1's height is set to 0
  2. the spacing between the two labels was automatically created by the stack view as 2 pixels high at 1000 priority.
  3. label2's height is set to 0
  4. the innerStackView's height is set to 0
  1. label1 的高度设置为 0
  2. 两个标签之间的间距由堆栈视图自动创建为 2 个像素高,优先级为 1000。
  3. label2 的高度设置为 0
  4. innerStackView 的高度设置为 0

The stack view cannot both be 0 pixels high and have its contents be 2 pixels high. The constraints cannot be satisfied.

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

Note: You can see this behavior with a simpler example. Simply add a UIView to a stack view as an arranged subview. Then set a height constraint on that UIView with 1000 priority. Now try calling hide on that.

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

Note: For whatever reason, this only happened when my stack view was a subview of a UICollectionViewCell or UITableViewCell. However, you can still reproduce this behavior outside of a cell by calling innerStackView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)on the next run loop after hiding the inner stack view.

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

Note: Even if you try executing the code in a UIView.performWithoutAnimations, the stack view will still add a 0 height constraint which will cause the USSC warning.

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



There are at least 3 solutions to this problem:

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

  1. Before hiding any element in a stack view, check if it's a stack view, and if so, change the spacingto 0.This is annoying because you need to reverse the process (and remember the original spacing) whenever you show the content again.
  2. Instead of hiding elements in a stack view, call removeFromSuperview.This is even more annoying since when you reverse the process, you need to remember whereto insert the removed item. You could optimize by only calling removeArrangedSubview and then hiding, but there is a lot of bookkeeping that still needs to be done.
  3. Wrap nested stack views (which have non-zero spacing) in a UIView. Specify at least one constraint as non-required priority (999 or below).This is the best solution since you don't have to do any bookkeeping. In my example, I created top, leading, and trailing constraints at 1000 between the stack view and the wrapper view, then created a 999 constraint from the bottom of the stack view to the wrapper view. This way when the outer stack view creates a zero height constraint, the 999 constraint is broken and you don't see the USSC warning. (Note: This is similar to the solution to Should the contentView.translatesAutoResizingMaskToConstraints of a UICollectionViewCell subclass be set to false)
  1. 在堆栈视图中隐藏任何元素之前,请检查它是否是堆栈视图,如果是,则将spacing其更改为 0。这很烦人,因为每当您再次显示内容时,您都需要反转该过程(并记住原始间距)。
  2. 不是在堆栈视图中隐藏元素,而是调用removeFromSuperview. 这更令人讨厌,因为当您反转该过程时,您需要记住在哪里插入已删除的项目。您可以通过仅调用 removeArrangedSubview 然后隐藏来优化,但仍有很多簿记需要完成。
  3. 将嵌套的堆栈视图(具有非零值spacing)包装在 UIView 中。将至少一个约束指定为非必需优先级(999 或更低)。这是最好的解决方案,因为您不必进行任何簿记。在我的示例中,我在堆栈视图和包装视图之间在 1000 处创建了顶部、前导和尾随约束,然后从堆栈视图底部到包装视图创建了 999 约束。这样,当外部堆栈视图创建零高度约束时,999 约束被破坏并且您看不到 USSC 警告。(注意:这类似于将UICollectionViewCell 子类的 contentView.translatesAutoResizingMaskToConstraints 设置为false的解决方案)


In summary, the reasons you get this behavior are:

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

  1. Apple automatically creates 1000 priority constraints for you when you add managed subviews to a stack view.
  2. Apple automatically creates a 0-height constraint for you when you hide a subview of a stack view.
  1. 当您将托管子视图添加到堆栈视图时,Apple 会自动为您创建 1000 个优先级约束。
  2. 当您隐藏堆栈视图的子视图时,Apple 会自动为您创建一个 0 高度约束。

Had Apple either (1) allowed you to specify the priority of constraints (especially of spacers), or (2) allowed you to opt-out of the automatic UISV-hidingconstraint, this problem would be easily resolved.

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

回答by Luciano Almeida

Most of the time, this error can be resolved by lowering the constraints priority in order to eliminate conflicts.

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

回答by Oren

When you set a view to hidden, the UIStackviewwill try to animate it away. If you want that effect, you'll need to set the right priority for the constraints so they don't conflict (as many has suggested above).

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

However if you don't care for the animation (perhaps you're hiding it in ViewDidLoad), then you can simple removeFromSuperviewwhich will have the same effect but without any issues with constraints since those will be removed along with the view.

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

回答by Peter Coyle

First, as others have suggested, make sure the constraints that you can control, i.e. not the constraints inherent to UIStackView are set to priority 999 so they can be overridden when the view is hidden.

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

If you are still experiencing the issue, then the problem is likely due to the spacing in the hidden StackViews. My solution was to add a UIView as a spacer and set UIStackView spacing to zero.Then set the View.height or View.width constraints (depending on a vertical or horizontal stack) to the spacing of the StackView.

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

Then adjust the content hugging and content compression resistance priorities of your newly added views. You might have to change the distribution of the parent StackView as well.

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

All of the above can be done in Interface Builder. You might additionally have to hide/unhide some of the newly added views programmatically so you do not have unwanted spacing.

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

回答by Adrian

I recently wrestled with auto layout errors when hiding a UIStackView. Rather than do a bunch of book keeping and wrapping stacks in UIViews, I opted to create an outlet for my parentStackViewand outlets for the children I want to hide/unhide.

我最近在隐藏UIStackView. 我没有做一堆簿记和包装堆栈UIViews,而是选择为我parentStackView和我想隐藏/取消隐藏的孩子们创建一个出口。

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

In storyboard, here's what my parentStack looks like:

在故事板中,这是我的 parentStack 的样子:

enter image description here

在此处输入图片说明

It has 4 children and each of the children have a bunch of stack views inside of them. When you hide a stack view, if it's got UI elements that are stack views as well, you'll see a stream of auto layout errors. Rather than hide, I opted to remove them.

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

In my example, parentStackViewscontains an array of the 4 elements: Top Stack View, StackViewNumber1, Stack View Number 2, and Stop Button. Their indices in arrangedSubviewsare 0, 1, 2, and 3, respectively. When I want to hide one, I simply remove it from parentStackView'sarrangedSubviewsarray. Since it's not weak, it lingers in memory and you can just put it back at your desired index later. I'm not reinitializing it, so it just hangs out until it's needed, but doesn't bloat memory.

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

So basically, you can...

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

1) Drag IBOutlets for your parent stack and the children you want to hide/unhide to the storyboard.

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

2) When you want to hide them, remove the stack you want to hide from parentStackView'sarrangedSubviewsarray.

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

3) Call self.view.layoutIfNeeded()with UIView.animateWithDuration.

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

Note the last two stackViews are not weak. You need to keep them around for when you unhide them.

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

Let's say I want to hide stackViewNumber2:

假设我想隐藏 stackViewNumber2:

parentStackView.removeArrangedSubview(stackViewNumber2)
stackViewNumber2.removeFromSuperview()

Then animate it:

然后动画它:

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

If you want to "unhide" a stackViewNumber2later, you can just insert it in the desired parentStackViewarrangedSubViewsindex and animate the update.

如果您想stackViewNumber2稍后“取消隐藏” ,您只需将其插入所需的parentStackViewarrangedSubViews索引并为更新设置动画。

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)

I found that to be a lot easier than doing bookkeeping on constraints, fiddling with priorities, etc.

我发现这比对约束进行簿记、摆弄优先级等要容易得多。

If you have something you want hidden by default, you could just lay it out on storyboard and remove it in viewDidLoadand update without the animation using view.layoutIfNeeded().

如果默认情况下您想隐藏某些内容,则可以将其放在故事板上,然后将其删除viewDidLoad并使用view.layoutIfNeeded().

回答by Will Stevens

I experienced the same errors with embedded Stack Views, though everything worked fine at runtime.

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

I solved the constraint errors by hiding all the sub-stack views first (setting isHidden = true) before hiding the parent stack view.

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

Doing this did not have all the complexity of removing sub arranged views, maintaining an index for when needing to add them back.

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

Hope this helps.

希望这可以帮助。

回答by Linh Ta

Senseful have provided an excellent answer to the root of the problem above so I'll go straight to the solution.

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

All you need to do is set all stackView constraints priority lower than 1000 (999 will do the work). For example if the stackView is constrained left, right, top, and bottom to its superview then all 4 constraints should have the priority lower than 1000.

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

回答by Ben Packard

Based on @Senseful's answer, here is a UIStackView extension to wrap a stack view in a view and apply the constraints he or she recommends:

根据@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
}

Instead of adding your stackView, use stackView.wrapped().

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

回答by sp00ky

I wanted to hide whole UIStackView's at a time but I was getting the same errors as the OP, this fixed it for me:

我想一次隐藏整个 UIStackView,但我遇到了与 OP 相同的错误,这为我解决了这个问题:

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