How do you add internal padding 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.
Last updated: Nov 8, 2022
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:
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:
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:
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:
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:
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:
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”:
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:
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.