UIStackView “Unable to simultaneously satisfy cons

2019-01-21 07:24发布

问题:

When my UIStackView "rows" are squished, they throw AutoLayout warnings. 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; (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) (

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?

回答1:

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.

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.

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-hiding constraint can then take priority if needed.



回答2:

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.

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

V:|[innerStackView]|              | = outerStackView

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

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

To understand why, let's first see why this doesn't happen when innerStackView.spacing is equal to 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). 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:

  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

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

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

  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

The stack view cannot both be 0 pixels high and have its contents be 2 pixels high. 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. Then set a height constraint on that UIView with 1000 priority. Now try calling hide on that.

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.

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.


There are at least 3 solutions to this problem:

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

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.

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.



回答3:

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



回答4:

When you set a view to hidden, the UIStackview will 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).

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.



回答5:

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:

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



回答6:

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.

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.

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.

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.



回答7:

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 parentStackView and outlets for the children I want to hide/unhide.

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

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

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.

In my example, parentStackViews contains an array of the 4 elements: Top Stack View, StackViewNumber1, Stack View Number 2, and Stop Button. Their indices in arrangedSubviews are 0, 1, 2, and 3, respectively. When I want to hide one, I simply remove it from parentStackView's arrangedSubviews array. 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.

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

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

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

Let's say I want to hide 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.

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



回答8:

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.

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.



回答9:

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). check the constraints of the UI objects in different size classes and see if there are anomalies with the constraints. 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:

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


回答11:

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.



回答12:

This error has nothing to do with 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. Obvious there are two conflicting constrains. The solution is to delete on of them.



回答13:

NOP with [mySubView removeFromSuperview]. I hope it could help someone :)