Content Hugging and Compression Resistance Explained

This post takes a detailed look at Content Hugging Priority and Compression Resistance Priority. It is an interactive discussion with examples so if you can perform the steps in this post yourself, you’ll probably learn more. The project I used is here in GitHub in its completed state, so you can grab it and remove controls in the stack view if you want to follow the steps in this post.

When an app is running, the views can be subjected to many changes in size. These result from various factors including device screen sizes, size classes, rotation, dynamic types, app content changes, and internationalization, among others.

We’ll talk about intrinsic content size further on in this post, but even without any of the “changes” in size while an app is running, a given view may be inherently needing to grow or shrink one or more controls because of the intrinsic sizes of controls exceeding available space or not needing the available space.

In a given view, there may be extra space horizontally and not enough vertically, or vice versa. There may also be extra or not enough in both directions.

Suppose extra space is available for a view. Which control (subview) should grow to take the extra space? For that control, set hugging priority lower, and you are telling Auto Layout not to hug that control. In other words, that is the control you would like to grow.

Lower hugging priority means grow bigger. Higher hugging priority means stay the same and resist getting bigger.

Suppose there is not enough space for a view. Which control (subview) should allow itself to be shrunk or compressed? For that control, set compression resistance priority lower, and you are telling Auto Layout that it can shrink this control because it is the one with the least resistance to being compressed.

Lower compression resistance means compress and make smaller. Higher compression resistance means stay the same and resist getting smaller.

These are the tenets of Content Hugging Priority and Compression Resistance Priority. Auto Layout needs you to tell it how to handle cases of more or less space, otherwise the results may be unexpected and undesirable when the size of a view changes or doesn’t match intrinsic content sizes.

Many controls have an intrinsic content size, which means the size it would like to be in order to accommodate its content. For some controls, such as UIView, there is no intrinsic content size. Some controls have both a height and width intrinsic content size. Controls with both height and width intrinsic content size include UILabel and UIButton.

In general, controls have a default value of 250 for horizontal and vertical Content Hugging Priority, and a default value of 750 for horizontal and vertical Compression Resistance Priority. More about that shortly, but based on those defaults it is therefore easier to stretch than it is to shrink.

Let’s walk through a project and clearly illustrate the concepts, starting with a standard Single View Application from the templates.

Add a UIStackView to the top level View. Create constraints to bind it to the leading, trailing, top, and bottom with constants of 5 and not relative to the margin. Set Stack View > Spacing to 10. It should already default to Axis > Vertical, and Fill for Distribution and Alignment.

To the UIStackView, add a UILabel, then a UITextField below that. Add some text to the UITextField, such as “This is a text field”. Resolve any misplaced views by updating frames.

You should notice that the text field has taken all of the extra space. Whether horizontal or vertical when you have a label and text field together, you’ll see the same result. This is not by accident.

It is common to have labels and text fields together in a view. Usually in such a case, you want the labels to hug their content. This scenario is so common that Interface Builder defaults a UILabel to have Content Hugging Priority values that are 251 instead of the normal default of 250. For this reason, you may have built many interfaces and not had to change default values. This is precisely why in our simple case so far, the label is sized according to its content, and the UITextField is taking all of the space beyond the label. The UITextField defaults to content hugging of 250 in both directions, so as we covered already, lower hugging means the control should be the one to grow and take space. This is exactly what is happening in this case.

With everything configured as described, in the Storyboard you should now have the following.

Screen Shot 2016-05-26 at 6.07.35 PM

Let’s make this more interesting by adding a UIImageView below the UITextField. Set the View > Mode of the UIImageView to Aspect Fit so that any images added will retain their height to width ratio and not become skewed.

Resolve all warnings by updating frames for misplaced views, and you’ll see that the text field is now hugging its content, and the UIImageView is taking all the extra space. We’ll discuss that in a moment.

A UIImageView is good to include because there are some interesting specifics in the context of Auto Layout, Content Hugging, Compression Resistance, and UIStackView when a UIImageView is in play.

A UIImageView without an image has no intrinsic content size. Once you add an image, then it has the intrinsic content size based on the image size. In our case, currently there is no image and therefore auto layout does not use the priorities, so even though UIImageView defaults to hugging values of 251, these have no impact. Auto Layout hugs the label and text field and gives all the extra space to the UIImageView. This one is somewhat of an anomaly but it is the result of there being no intrinsic size for the UIImageView. It will change once we add an image.

In my project, there are two images. One is 300 x 300 (IosInsight.png), and the other is 1500 x 1999 (Sunset.png). I’ve made these sizes on purpose to illustrate the impact of images being smaller versus larger than the space available. If you are following along in your own project from scratch, then grab these images from my GitHub project, and add them to your project.

Set the Image View > Image to IosInsight.png, and then resolve any misplaced views. You’ll notice that the UIImageView is now sized to accommodate the image according to its actual size, and the text field is now back to using the extra space available. You’ll also see that the UIImageView reflects the Distribution Fill setting for the UIStackView, but that the image doesn’t take up the entire width. This is because of the Aspect Fit setting, where the height is 300, and therefore the width is also 300, but since the screen width is more, some portion of the UIImageView is simply empty since we’ve told it not to stretch the image, but rather to maintain its aspect ratio. UIImageView achieves this by just having empty space outside the image.

In the above case, we are working with a scenario where there is extra space to allocate. Here is what it looks like in the Storyboard.

Screen Shot 2016-05-26 at 7.34.24 PM

One key thing to note so far is that we haven’t set any Compression Resistance values, so they are all defaulted. Auto Layout is happy, because it didn’t have to compress any view (ie. component or control), so there was no trouble from not having explicitly set values that deviated from the defaults.

Let’s see how Auto Layout reacts to switching the image to the larger one, Sunset.png. This is really important for understanding the relationship between Content Hugging/Compression Resistance, and intrinsic content size. Go ahead and change the image for the UIImageView.

Auto Layout is no longer happy, and has found the layout to be unsatisfiable, or in other words, it has conflicting constraints. The good news it you can see what those are by clicking the red arrow.

In this case, with the larger image, we are now facing a scenario where there is not enough space, versus the case of extra space for the previous image. When there isn’t enough space, as mentioned in the opening narrative of this post, Auto Layout needs you to tell it what it can compress.

If you look at the recommendations provided when clicking on the red arrow, you’ll see that it recommends setting vertical compression resistance higher for the label and text field, or lower for the image view. We’ll choose setting the image view lower and that will result in the image being resized (compressed) to fit the available space, while maintaining its aspect ratio due to the setting of Aspect Fit. The label and text field can remain at 250 since they will be higher than the image view, and thus will not be compressed.

Once you make that change to 251 for the image view, and resolve misplaced frames, you will see the following, and can also run it to see it in action. The image has been compressed just enough so that the label and text field are respecting their intrinsic content sizes.

Screen Shot 2016-05-26 at 7.58.51 PM

Let’s look at one more aspect of this example by going back to the other image, IosInsight.png. Change back to it, and resolve mismatched frames. Note: There are times when you may need to close the storyboard, and open it again in order to resolve the mismatches.

At this point we’ve encountered the case where there isn’t enough space and something needs to be compressed, so we set compression resistance on the UIImageView for that purpose. When the image is switched back to the smaller one, then we see that the extra space is back to being assigned to the UITextView. In reality, this is not likely to be the desired result. Instead, we’d want the image to grow and take the extra space. We could have handled this originally when we used the IosInsight.png image but I wanted to show the actions needed separately to highlight the two cases of extra versus less space. Right now, you should see this with the IosInsight.png image specified.

Screen Shot 2016-05-27 at 8.30.37 AM

Remember that the default for hugging priority is 251 for UIImageView and UILabel, but is 250 for UITextField. To give the extra space to the image view, its hugging priority needs to decrease to signify that it should grow when there is space available. The hugging priority of the text field should be increased so that it does not grow.

Go ahead and increase hugging priority of the text field to 251, and decrease the image view to 250. Resolve misplaced frames and you’ll see that the image view now takes the extra space, and the label and text field both hug their content. This is the outcome in the Storyboard.

Screen Shot 2016-05-27 at 8.58.42 AM

All of the changes made can be run in the simulator and you’ll see roughly the same results as you see in the Storyboard, however the exact result depends on which device you choose.

With the iPhone 4s versus an iPhone 6, you’ll see that the bigger increase in vertical space for the iPhone 6, compared to the increase in horizontal space, results in there being more empty vertical space. This is due to the aspect ratio needing to be maintained, so when the width reaches the maximum screen size, height will also stop growing. Here are screenshots of the 4s on the left, and the 6 on the right. I’ve changed the background color of the UIImageView to a light blue to make it obvious.

i4sScreen
i6Screen

A mentioned above, you can grab the project here on GitHub to experiment with it.

Leave a Comment: