Stack View Constraint Conflicts When Hiding Views

The Stack View is a great timesaver when it comes to Auto Layout but it is not totally pain free. Having a stack view create and manage the constraints for you is great until something goes wrong. Debugging an unable to simultaneously satisfy constraints error is hard when you don’t know what the stack view is doing. A common cause of stack view auto layout conflicts is hiding views that have an explicit size constraint.

Creating a Conflict

I got a little lazy when creating a stack view for last weeks post on refresh controls. This is what I ended up (with some extra borders added to each view for clarity):

Stack view

The stack view is vertical with a fill alignment and distribution and some extra spacing between the four views (two labels, two buttons). To add some extra padding to the two labels I added a fixed height constraint of 128 points:

Fixed Height Constraints

To make it easier to debug I added an identifier to the constraint named OrangeLabelHeight:

Constraint Identifier

The UIStackView documentation hints of trouble ahead:

Be careful to avoid introducing conflicts when adding constraints to views inside a stack view. As a general rule of thumb, if a view’s size defaults back to its intrinsic content size for a given dimension, you can safely add a constraint for that dimension.

What is a UISV-hiding constraint?

I toggle the visibility of the orange button and label with a property in the view controller that changes the isHidden property of each view:

private var orangeAvailable = true {
  didSet {
    orangeLabel.isHidden = !orangeAvailable
    orangeButton.isHidden = !orangeAvailable
  }
}

This is what I see on the console when I hide the orange label (truncated for brevity):

Unable to simultaneously satisfy constraints.

"<NSLayoutConstraint:0x60800008c1c0 'OrangeLabelHeight'
UILabel:0x7fc178f092b0'Orange Membership\nFor som...'.height == 128
(active)>",
"<NSLayoutConstraint:0x60800008d930 'UISV-hiding'
UILabel:0x7fc178f092b0'Orange Membership\nFor som...'.height == 0
(active)>"

The conflict is clear. I have two constraints both trying to set the height of the orange label. My constraint OrangleLabelHeight wants 128 points and another named UISV-hiding added by the stack view wants 0 points. If we use the view debugger we can also see that both constraints have a priority of 1000 meaning they are required:

Constraint priorities

This suggest the way to remove the conflict. Lower the priority of my constraint to be less than 1000:

Priority 999

A better solution in this case would be to create a custom label view with the extra padding and remove the explicit height constraint. If you find you do need such constraints remember you can lower their priority to avoid conflicts with the stack view.

Sample Code

You can find the example layout from this post in the RefreshScroll project in my GitHub repository.