UIStackView "Unable to simultaneously satisfy constraints" on "squished" hidden views
Asked Answered
T

14

103

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?

Territerrible answered 6/9, 2015 at 20:54 Comment(1)
This appears to be fixed in iOS11, not getting any warnings hereCanary
E
212

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.

Engineer answered 1/10, 2015 at 11:2 Comment(6)
it should be noted that seems like if you just have a height or width constraint on these and reduce their priority it won't work. You need to remove the height/width and add the top leading trailing bottoms, then set their priority as lower and it worksLongo
Changing the priorities also worked for me. Also, removing any excess (dimmed) Constraints that were accidentally copied over from unused Size Classes. IMPORTANT TIP: to more easily debug these problems, set an IDENTIFER string on each Constraint. Then you can see which Constraint was being naughty in the debug message.Frozen
In my case, I only have to lower the priority on the height and it works.Wickiup
That IDENTIFIER tip is great! I've always wondered how to give the constraints in the debug message names, I was always looking to add something to the view rather than the constraint itself. Thanks @Womble!Lagas
Thanks! Priority from 1000 to 999 did the trick.Xcode: Version 8.3.3 (8E3004b)Ettieettinger
Occurs on iOS 11 too :(. Lowering priority worked, but not sure why this occurs for me only when inside a cell.Shae
P
58

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.

Ptolemaist answered 27/6, 2016 at 23:17 Comment(4)
Thanks for the super thorough and helpful explanation. This definitely however seems like a bug on Apple's side with stack views. Basically their "hiding" feature is incompatible with their "spacing" feature. Any ideas of they have resolved this since, or have added some features to prevent hacking around with extra containing views? (Again, great breakdown of potential solutions and do agree on the elegance of #3)Unintelligible
Does every singleUIStackView child of a UIStackView need to be wrapped in a UIView or just the one you're seeking to hide/unhide?Waxen
This feels like a really bad oversight on Apple's part. Especially because warnings and errors surrounding the use of UIStackView tend to be cryptic and hard to understand.Interlocutrix
This is a lifesaver. I was having a problem where UIStackViews added to a UITableViewCell were causing AutoLayout error log spamming, every time the cell was reused. Embedding the stackView in a UIView with its bottom anchor constraint priority set to low, solved the problem. It does cause the view debugger to show the stackView's elements as having ambiguous heights, but it shows properly in the app, without error log spam. THANK YOU.Frozen
R
6

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

Reynold answered 6/9, 2015 at 21:42 Comment(3)
What do you mean? The constraints are all relative within the views that are stacked....Territerrible
I'm sorry, i don't understand your question right,my english is so so, but what i think is that constraints are relative to the view witch it is associated, if the view become hidden the constraints became inactive. I'm not sure if that is your doubt, but i hope i can help.Reynold
It seems to be coming from when the stuff is squished or in the process of being shown/hidden. In this case, it becomes partially visible. — Maybe it is necessary to actually go through and eliminate any minimum vertical constants because it can be squished to 0 height?Territerrible
G
2

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.

Garbo answered 5/9, 2016 at 18:32 Comment(0)
K
1

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

Kiele answered 4/8, 2016 at 18:23 Comment(0)
E
1

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.

Eddieeddina answered 11/9, 2016 at 0:26 Comment(0)
W
1

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:

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.

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

Waxen answered 17/3, 2017 at 15:31 Comment(0)
W
1

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.

Wiggins answered 23/4, 2017 at 19:9 Comment(0)
P
1

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

Pruchno answered 25/2, 2019 at 8:24 Comment(0)
M
0

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 :/

Mesnalty answered 6/9, 2015 at 22:28 Comment(2)
Oh ok. but I got that error when I had the case I described drive.google.com/file/d/0B-mn7bZcNqJMVkt0OXVLVVdnNTA/…Mesnalty
Yeah I'm definitely not seeing any red whatsoever by toggling size classes in interface builder. I only used the "Any" size.Territerrible
L
0

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];
    }
}
Lanza answered 14/3, 2016 at 15:15 Comment(1)
This didn't work for me because the auto layout engine complains when you change a required constraint (priority = 1000) to not required (priority <= 999).Ptolemaist
E
0

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.

Erective answered 7/10, 2016 at 12:25 Comment(0)
D
-2

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.

Disloyal answered 14/9, 2015 at 14:49 Comment(0)
R
-3

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

Rescission answered 9/8, 2016 at 4:45 Comment(1)
Sorry, what's NOP?Loomis

© 2022 - 2024 — McMap. All rights reserved.