Adding Padding To A Stack View

How do you add padding internal to a stack view? How about when the stack view has readable content and you want to limit the line length? You can do it in Interface Builder or in code but both have oddities. Stack view readable content guides certainly don’t work the way I would expect or hope.

Stack View Margins

The whole point of a stack view is that it creates the constraints for its arranged subviews. This makes it easy to forget that a stack view has margins that you can use to add internal padding. The way you use the margins depends on whether you use Interface Builder or create your layout in code.

Margins In Interface Builder

Here’s my stack view created in Interface Builder. It has a label and a text view arranged vertically with default .fill distribution and alignment and a standard amount of spacing:

Stack view in Interface Builder

I am using dynamic type for the text so I have my stack view embedded in a scroll view. This is how the layout looks running on an iPhone 8:

iPhone 8

The text is tight against the edges. Let’s use the stack view margins to add some padding. Select the stack view and use the size inspector to configure the layout margins. I added 20 points to each edge:

Layout margins

Note: Apple added directional layout margins in iOS 11. If you need to support iOS 10 or earlier use the fixed layout margins.

I added a red background view to the stack view so you can see its bounds with the 20 point margin to the content:

Stack view with margin

Margins In Code

If you are creating this stack view in code:

let stackView = UIStackView(arrangedSubviews: [chapterLabel, textView])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = UIStackView.spacingUseSystem

Configure the stack view so that it lays out its arranged subviews relative to its layout margins instead of its edges:

stackView.isLayoutMarginsRelativeArrangement = true

Then add the layout margins:

stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)

If you are supporting iOS 10 or earlier set the layoutMargins instead of the directionalLayoutMargins:

stackView.layoutMargins = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)

That is not so bad but we want to be using the readable content guide not the (directional) layout guide when working with text.

Stack Views and Readable Content

A stack view also has a readable content guide but you don’t use it directly. In Interface Builder you select the “Follow Readable Width” property in the size inspector for the stack view:

Follows readable width

This limits the width of the label and text view to a readable line length based on the user’s preferred text content size. Here is the result on an iPad Pro 10.5”. Note the stack view (in red) still extends to the view edges:

iPad readable content

How about if we are building this layout in code? How do we tell the stack view to use the readable content guide? This is where things get awkward…

If you are familiar with setting the readable width of table view cells you may remember a table view has a property you can set to get readable width margins:

tableView.cellLayoutMarginsFollowReadableWidth = true

You can also use the readableContentGuide directly when you constrain the content to the table view’s contentView. Neither of these is possible with a stack view.

We are not responsible for constraining the arranged subviews of the stack view and there is no configuration option to tell the stack view to use the readable content guide.

One way is to add constraints to fix the width of the text view and text label to the readable width. This seems to be what is happening in Interface Builder when you select “Follow Readable Width”:

Debug view

I am not a fan of adding extra constraints to views managed by the stack view. It is too easy to make a false assumption about the stack view implementation and break something.

That leaves us with the workaround of borrowing the readable content guide from a parent view. For example, I could constrain my scroll view to the readable content guide of the root view:

let guide = view.readableContentGuide
NSLayoutConstraint.activate([
  scrollView.leadingAnchor.constraint(equalTo: guide.leadingAnchor),
  ...

This gets close but is not an exact match of the Interface Builder layout:

Readable stack view

Note that the red background view of the stack view no longer extends to the edges of the super view. If that is important then we need a slightly different approach. We can embed the stack view in a container view which also serves as the background:

private lazy var containerView: UIView = {
  let view = UIView()
  view.translatesAutoresizingMaskIntoConstraints = false
  view.backgroundColor = .red
  view.addSubview(stackView)

We move the 20 point margin from the stack view to the container view:

  view.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)

Then constrain the stack view to the readable content guide of the container view:

  let readable = view.readableContentGuide
  NSLayoutConstraint.activate([
    stackView.leadingAnchor.constraint(equalTo: readable.leadingAnchor),
    stackView.topAnchor.constraint(equalTo: readable.topAnchor),
    stackView.trailingAnchor.constraint(equalTo: readable.trailingAnchor),
    stackView.bottomAnchor.constraint(equalTo: readable.bottomAnchor),
    ])
    return view
}()

Note that we now embed the container view, not the stack view, in the scroll view.

Stack View Support For Readable Content

Having to embed the stack view in a container view just to get a readable width is extra work. What I would like to see is for Apple to add a flag to stack view that makes its margins follow the readable width (similar to the way it works for a table view):

// Please UIKit engineers can we have this...
stackView.layoutMarginsFollowReadableWidth = true

I created rdar://44303114 if you would like to duplicate the suggestion.

Sample Code

The sample code for this post containing both the Interface Builder and programmatic layout versions is in my GitHub repository.

Want To Learn More?

If you have read this far you are likely to be interested in my book - Modern Auto Layout.

Further Reading

Never miss a post

iOS Size Classes Cheat Sheet

Subscribe and also get my free iOS Size Classes Cheat Sheet

Success! Now check your email to confirm your subscription and download your free guide to iOS Size Classes.

There was an error submitting your subscription. Please try again.

Unsubscribe at any time. See Privacy Policy
No time to watch WWDC videos?

Sign up to get my iOS posts and news direct to your inbox and also get my free iOS Size Classes Cheat Sheet

OK! Check your inbox (or spam folder) for an email to confirm your details and download your free guide to iOS Size Classes.

There was an error submitting your subscription. Please try again.

Unsubscribe at any time. See Privacy Policy
Archives Categories