Ever wondered what the “Preserve Superview Margins” checkbox does in Interface Builder? When should you use it and why does it not seem to do anything most of the time? Here are my notes along with an extra step you will need to make it work with stack views.
Last updated: Jun 12, 2020
Here is the super simple setup to make this easy to see. I have a red container view pinned to the leading and trailing edges of the root view of a view controller. A yellow content view has constraints to the margins of the red view:
The default (“standard”) margin for a view is 8 points on each side (left, right, top, bottom). This is what you get when you choose “Constrain to Margins” in Interface Builder:
If we were adding the constraints in code here is how we would add the yellow view with constraints to the margins of the red view:
let yellowView = ... redView.addSubview(yellowView) let redMargins = redView.layoutMarginsGuide NSLayoutConstraint.activate([ yellowView.leadingAnchor.constraint(equalTo: redMargins.leadingAnchor), yellowView.trailingAnchor.constraint(equalTo: redMargins.trailingAnchor), yellowView.topAnchor.constraint(equalTo: redMargins.topAnchor), yellowView.bottomAnchor.constraint(equalTo: redMargins.bottomAnchor) ])
Note: You could not change the margins of the root view before iOS 11. The systems sets the margins of the root view depending on the size class. The top and bottom margins are 0 points and the leading and trailing margins are 16 or 20 points depending on the size class.
Preventing Content in the Superview Margin
I have an enlarged picture of the margins below. Note that the yellow content view is 8 points (the default margin of the red view) from the left edge. It overlaps the 16 point margin of the root view that has been set by the system:
This is the situation where the
preservesSuperviewLayoutMargins property of
UIView makes a difference. From the documentation:
When the value of this property is YES, the superview’s margins are also considered when laying out content. This margin affects layouts where the distance between the edge of a view and its superview is smaller than the corresponding margin.
If we set it on the red container view it increases the effective margin of the red view to match the superview margin. Any subviews we add to the red view and constrain with that margin will never overlap with the superview margin.
You can set the property in Interface Builder:
Or in code:
redView.preservesSuperviewLayoutMargins = true
The result is to increase the effective margin of the red view, moving the yellow content away from the edge. It does not change the top margin since the 8 point margin of the red view is already larger than the zero point top margin of the root view:
Note the specific circumstances where this property has any effect:
- the container view fills its superview (constrained to edges).
- the content view has constraints to the margins of the container view.
- the container view margins are smaller than the margins of its superview.
If the content view has constraints to the edges of the container the assumption is that you do not care about margins and the
preservesSuperviewLayoutMargins property has no effect.
Stack View - Using Margins
Things are a little more complicated if your content view is a stack view. I added a stack view to the bottom half of the screen with blue and green views. Distribution is fill equally and spacing is 8 points. As with the red view we pin the stack view to the edges of the root view:
preservesSuperviewLayoutMargins has no effect though which is confusing. The reason is that, by default, the arranged subviews of the stack view have constraints with the edges of the stack view and not the margins. Luckily the
isLayoutMarginsRelativeArrangement property of a stack view allows us to change that. From the documentation:
If true, the stack view will layout its arranged views relative to its layout margins. If false, it lays out the arranged views relative to its bounds. The default is false.
There is no direct way to set that property in Interface Builder. Instead you can add a fixed layout margin for the stack view (you can set the insets to zero if you do not want the extra padding):
The stack view now respects the super view margin and is inset 16 points from the sides (it also has 8 points of padding top and bottom from the stack view margin):
If you were adding the stack view in code:
stackView.preservesSuperviewLayoutMargins = true stackView.isLayoutMarginsRelativeArrangement = true
If you want the extra padding:
stackView.layoutMargins = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
Get the Code
This is all very confusing so you may want to play with the Xcode project until it makes sense.