I have a complex view hierarchy, built in Interface Builder, with nested UIStackViews. I get "unsatisfiable constraints" notices every time I hide some of my inner stackviews. I've tracked it down to this:
( "<NSLayoutConstraint:0x1396632d0 'UISV-canvas-connection' UIStackView:0x1392c5020.top == UILabel:0x13960cd30'Also available on iBooks'.top>", "<NSLayoutConstraint:0x139663470 'UISV-canvas-connection' V:[UIButton:0x139554f80]-(0)-| (Names: '|':UIStackView:0x1392c5020 )>", "<NSLayoutConstraint:0x139552350 'UISV-hiding' V:[UIStackView:0x1392c5020(0)]>", "<NSLayoutConstraint:0x139663890 'UISV-spacing' V:[UILabel:0x13960cd30'Also available on iBooks']-(8)-[UIButton:0x139554f80]>" )
Specifically, the UISV-spacing
constraint: when hiding a UIStackView its high constraint gets a 0 constant, but that seems to clash with the inner stackview's spacing constraint: it requires 8 points between my Label and Button, which is irreconcilable with the hiding constraint and so the constraints crash.
Is there a way around this? I've tried recursively hiding all the inner StackViews of the hidden stack view, but that results in strange animations where content floats up out of the screen, and causes severe FPS drops to boot, while still not fixing the problem.
This is a known problem with hiding nested stack views.
There are essentially 3 solutions to this problem:
innerStackView.removeFromSuperview()
, but then you'll need to remember where to insert the stack view.The 3rd option is the best in my opinion. For more information about this problem, why it happens, the different solutions, and how to implement solution 3, see my answer to a similar question.
So, you have this:
And the problem is, when you first collapse the inner stack, you get auto layout errors:
2017-07-02 15:40:02.377297-0500 nestedStackViews[17331:1727436] [LayoutConstraints] 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. ( "<NSLayoutConstraint:0x62800008ce90 'UISV-canvas-connection' UIStackView:0x7fa57a70fce0.top == UILabel:0x7fa57a70ffb0'Top Label of Inner Stack'.top (active)>", "<NSLayoutConstraint:0x62800008cf30 'UISV-canvas-connection' V:[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...']-(0)-| (active, names: '|':UIStackView:0x7fa57a70fce0 )>", "<NSLayoutConstraint:0x62000008bc70 'UISV-hiding' UIStackView:0x7fa57a70fce0.height == 0 (active)>", "<NSLayoutConstraint:0x62800008cf80 'UISV-spacing' V:[UILabel:0x7fa57a70ffb0'Top Label of Inner Stack']-(8)-[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...'] (active)>" ) Will attempt to recover by breaking constraint <NSLayoutConstraint:0x62800008cf80 'UISV-spacing' V:[UILabel:0x7fa57a70ffb0'Top Label of Inner Stack']-(8)-[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...'] (active)> 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.
The problem, as you noted, is that the outer stack view applies a height = 0 constraint to the inner stack view. This conflicts with the 8 point padding constraint applied by the inner stack view between its own subviews. Both constraints cannot be satisfied simultaneously.
The outer stack view uses this height = 0 constraint, I believe, because it looks better when animated than just letting the inner view be hidden without shrinking first.
There's a simple fix for this: wrap the inner stack view in a plain UIView
, and hide that wrapper. I'll demonstrate.
Here's the scene outline for the broken version above:
To fix the problem, select the inner stack view. From the menu bar, choose Editor > Embed In > View:
Interface Builder created a width constraint on the wrapper view when I did this, so delete that width constraint:
Next, create constraints between all four edges of the wrapper and the inner stack view:
At this point, the layout is actually correct at runtime, but Interface Builder draws it incorrectly. You can fix it by setting the vertical hugging priorities of the inner stack's children higher. I set them to 800:
We haven't actually fixed the unsatisfiable constrain problem at this point. To do so, find the bottom constraint that you just created and set its priority to less than required. Let's change it to 800:
Finally, you presumably had an outlet in your view controller connected to the inner stack view, because you were changing its hidden
property. Change that outlet to connect to the wrapper view instead of the inner stack view. If your outlet's type is UIStackView
, you'll need to change it to UIView
. Mine was already of type UIView
, so I just reconnected it in the storyboard:
Now, when you toggle the wrapper view's hidden
property, the stack view will appear to collapse, with no unsatisfiable constraint warnings. It looks virtually identical, so I won't bother posting another GIF of the app running.
You can find my test project in this github repository.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With