简体   繁体   English

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

[英]UIStackView “Unable to simultaneously satisfy constraints” on “squished” hidden views

When my UIStackView "rows" are squished, they throw AutoLayout warnings. 当我的UIStackView“行”被AutoLayout ,它们将引发AutoLayout警告。 However, they display fine and nothing else is wrong besides these sorts of loggings: 但是,它们显示正常,除了这些日志记录之外,没有其他问题:

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; 尝试以下操作:(1)查看每个约束,并尝试找出不期望的约束; (2) find the code that added the unwanted constraint or constraints and fix it. (2)查找添加了一个或多个不必要约束的代码并进行修复。 (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints ) ( (注意:如果看到的是您不了解的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'标记,表明在这种情况下也许应该忽略子视图或某些东西的高度最小值?

You get this issue because when setting a subview from within UIStackView to 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 UIView within my UIStackView that contained a UISegmentedControl inset by 8pts on each edge. 我试图做的是将一个UIView放在我的UIStackView ,其中每个边上包含一个由8pts插入的UISegmentedControl

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. 当我将其设置为hidden时,它将尝试将容器视图限制为零高度,但是由于我从上到下都有一组约束,因此发生了冲突。

To resolve the issue, I changed my 8pt top an bottom constraints priority from 1000 to 999 so the UISV-hiding constraint can then take priority if needed. 为了解决该问题,我将8pt顶部底部约束的优先级从1000更改为999,因此,如果需要, UISV-hiding约束可以优先。

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't happen when innerStackView.spacing is equal to 0 . 要了解原因,首先让我们看看当innerStackView.spacing等于0时为什么不会发生这种情况。 When you call innerStackView.hidden = true , @liamnichols was correct... the outerStackView will magically intercept this call, and create a 0 height UISV-hiding constrain with priority 1000 (required). 当您调用innerStackView.hidden = true ,@ liamnichols是正确的…… outerStackView将神奇地拦截此调用,并创建优先级为1000的0高度UISV隐藏约束(必需)。 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. 假定这是为了让堆栈视图中的元素动画化,以防万一您在UIView.animationWithDuration()块中调用了隐藏代码。 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: 但是,由于发生以下情况,您不会收到“无法同时满足约束”(USSC)警告:

  1. label1's height is set to 0 label1的高度设置为0
  2. the spacing between the two labels was already defined as 0 两个标签之间的间距已经定义为0
  3. label2's height is set to 0 label2的高度设置为0
  4. the innerStackView's height is set to 0 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. 堆栈视图只是将所有内容拖到高度为0的像素中。

Now going back to the buggy example, if we set the spacing to 2 , we now have these constraints: 现在回到有问题的示例,如果将spacing设置为2 ,则现在具有以下约束:

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

The stack view cannot both be 0 pixels high and have its contents be 2 pixels high. 堆栈视图不能都为0像素高,并且其内容不能为2像素高。 The constraints cannot be satisfied. 约束无法满足。

Note: You can see this behavior with a simpler example. 注意:您可以通过一个更简单的示例看到此行为。 Simply add a UIView to a stack view as an arranged subview. 只需将UIView作为已安排的子视图添加到堆栈视图即可。 Then set a height constraint on that UIView with 1000 priority. 然后在具有1000优先级的UIView上设置高度限制。 Now try calling hide on that. 现在尝试在其上调用hide。

Note: For whatever reason, this only happened when my stack view was a subview of a UICollectionViewCell or UITableViewCell. 注意:无论出于何种原因,这仅在我的堆栈视图是UICollectionViewCell或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. 但是,您仍然可以在隐藏内部堆栈视图之后,在下一个运行循环中调用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 spacing to 0. This is annoying because you need to reverse the process (and remember the original spacing) whenever you show the content again. 在将任何元素隐藏在堆栈视图中之前,请检查它是否为堆栈视图,如果是,请将spacing更改为0。这很烦人,因为每当您再次显示内容时,您都需要反转该过程(并记住原始间距)。
  2. Instead of hiding elements in a stack view, call removeFromSuperview . 而不是在堆栈视图中隐藏元素,而是调用removeFromSuperview This is even more annoying since when you reverse the process, you need to remember where to 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. 您可以通过仅调用removeArrangedSubview然后隐藏来进行优化,但是仍有许多簿记工作需要完成。
  3. Wrap nested stack views (which have non-zero spacing ) in a UIView. 将嵌套的堆栈视图(其spacing为非零)包装在UIView中。 Specify at least one constraint as non-required priority (999 or below). 将至少一个约束指定为不需要的优先级(999或更低)。 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. 在我的示例中,我在堆栈视图和包装器视图之间的顶部,顶部和结尾约束为1000,然后从堆栈视图的底部到包装器视图创建了999约束。 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. 这样,当外部堆栈视图创建零高度约束时,999约束将被破坏,并且您不会看到USSC警告。 (Note: This is similar to the solution to Should the contentView.translatesAutoResizingMaskToConstraints of a UICollectionViewCell subclass be set to false ) (注意:这类似于将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. 将托管子视图添加到堆栈视图时,Apple会自动为您创建1000个优先级约束。
  2. Apple automatically creates a 0-height constraint for you when you hide a subview of a stack view. 当您隐藏堆栈视图的子视图时,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-hiding constraint, this problem would be easily resolved. 如果Apple(1)允许您指定约束的优先级(尤其是间隔 ),或者(2)允许您选择退出自动UISV隐藏约束,则可以轻松解决此问题。

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

When you set a view to hidden, the UIStackview will try to animate it away. 当您将视图设置为隐藏时, UIStackview将尝试对其进行动画处理。 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). 如果您想要这种效果,则需要为约束设置正确的优先级,以使它们不会发生冲突(如上文所述)。

However if you don't care for the animation (perhaps you're hiding it in ViewDidLoad), then you can simple removeFromSuperview which will have the same effect but without any issues with constraints since those will be removed along with the view. 但是,如果您不关心动画(也许您将其隐藏在ViewDidLoad中),则可以简单地执行removeFromSuperview ,效果相同,但没有任何约束问题,因为这些约束将与视图一起被删除。

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()

First, as others have suggested, make sure the constraints that you can control, ie 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. 如果您仍然遇到问题,则可能是由于隐藏的StackViews中的间距所致。 My solution was to add a UIView as a spacer and set UIStackView spacing to zero. 我的解决方案是添加一个UIView作为分隔符,并将UIStackView的间距设置为零。 Then set the View.height or View.width constraints (depending on a vertical or horizontal stack) to the spacing of the StackView. 然后将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. 以上所有操作都可以在Interface Builder中完成。 You might additionally have to hide/unhide some of the newly added views programmatically so you do not have unwanted spacing. 您可能还需要以编程方式隐藏/取消隐藏一些新添加的视图,以免出现不必要的间距。

I recently wrestled with auto layout errors when hiding a UIStackView . 我最近在隐藏UIStackView时遇到了自动布局错误。 Rather than do a bunch of book keeping and wrapping stacks in UIViews , I opted to create an outlet for my parentStackView and outlets for the children I want to hide/unhide. 不是做了一堆的记账和包装纸堆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的样子:

在此处输入图片说明

It has 4 children and each of the children have a bunch of stack views inside of them. 它有4个孩子,每个孩子里面都有很多堆栈视图。 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. 隐藏堆栈视图时,如果UI元素也是堆栈视图,则会看到一系列自动布局错误。 Rather than hide, I opted to remove them. 我选择隐藏而不是隐藏。

In my example, parentStackViews contains an array of the 4 elements: Top Stack View, StackViewNumber1, Stack View Number 2, and Stop Button. 在我的示例中, parentStackViews包含4个元素的数组:顶部堆栈视图,StackViewNumber1,堆栈视图编号2和停止按钮。 Their indices in arrangedSubviews are 0, 1, 2, and 3, respectively. 它们在arrangedSubviews中的索引分别为0、1、2和3。 When I want to hide one, I simply remove it from parentStackView's arrangedSubviews array. 当我想隐藏一个,我只是从中删除parentStackView's arrangedSubviews阵列。 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. 我没有重新初始化它,因此它只是一直挂到需要时才使用,但不会使内存膨胀。

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's arrangedSubviews array. 2)当你想隐藏,删除你想隐藏栈parentStackView's arrangedSubviews阵列。

3) Call self.view.layoutIfNeeded() with UIView.animateWithDuration . 3)使用UIView.animateWithDuration调用UIView.animateWithDuration self.view.layoutIfNeeded()

Note the last two stackViews are not weak . 注意最后两个stackViews不是weak You need to keep them around for when you unhide them. 当您取消隐藏它们时,需要将它们保留在周围。

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 stackViewNumber2 later, you can just insert it in the desired parentStackView arrangedSubViews index and animate the update. 如果你想“取消隐藏”一个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)

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 viewDidLoad and update without the animation using view.layoutIfNeeded() . 如果您有默认情况下想要隐藏的内容,则可以将其放在情节view.layoutIfNeeded()上,然后将其删除在viewDidLoad然后使用view.layoutIfNeeded()进行动画更新。

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. 希望这可以帮助。

You might have created a constraint while working with a certain size class (ex: wCompact hRegular) and then you created a duplicate when you switched to another size class (ex: wAny hAny). 您可能在使用某个尺寸等级(例如:wCompact hRegular)时创建了一个约束,然后在切换到另一个尺寸等级(例如:wAny hAny)时创建了一个重复项。 check the constraints of the UI objects in different size classes and see if there are anomalies with the constraints. 检查不同大小类中的UI对象的约束,并查看约束是否存在异常。 you should see the red lines indicating colliding constraints. 您应该看到红线表示冲突约束。 I can't put a picture until I get 10 reputation points sorry :/ 在获得10个信誉点之前,我无法贴图片://

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];
    }
}

I had a row of buttons with height constraint. 我有一排带有高度限制的按钮。 This happens when one button is hidden. 当一个按钮被隐藏时,会发生这种情况。 Setting the priority of that buttons height constraint to 999 have resolved the issue. 将按钮高度限制的优先级设置为999可解决此问题。

Senseful have provided an excellent answer to the root of the problem above so I'll go straight to the solution. 有道理为上述问题的根源提供了一个很好的答案,因此我将直接解决。

All you need to do is set all stackView constraints priority lower than 1000 (999 will do the work). 您需要做的就是将所有stackView约束的优先级设置为低于1000(999可以完成工作)。 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限制在其超级视图的左,右,上和下,则所有4个约束的优先级均应低于1000。

This error has nothing to do with UIStackView. 此错误与UIStackView没有关系。 It happens when you have conflict constrains with the same priorities. 当您具有相同优先级的冲突约束时,就会发生这种情况。 For example, if you have a constrain states that the width of your view is 100, and you have another constrain at the same time states that the view's width is 25% of its container. 例如,如果有一个约束说明视图的宽度为100,而同时有另一个约束则说明视图的宽度为其容器的25%。 Obvious there are two conflicting constrains. 显然有两个相互矛盾的约束。 The solution is to delete on of them. 解决的办法是删除它们。

NOP with [mySubView removeFromSuperview]. 使用[mySubView removeFromSuperview]进行NOP。 I hope it could help someone :) 我希望它可以帮助某人:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM