Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 508
Ben Guild Avatar asked Sep 06 '15 20:09

Ben Guild


2 Answers

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.

like image 156
liamnichols Avatar answered Oct 06 '22 16:10

liamnichols


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.

like image 25
Senseful Avatar answered Oct 06 '22 14:10

Senseful