Padding and UIStackViews
I'm a huge fan of stack views because they're super powerful and they do a lot of heavy lifting for us in terms of layout. What I want to show here is how we can add padding to our stack views in a clean way. Let's go!
Padding – SwiftUI
If you're familiar with SwiftUI, then I'm sure you've probably done something that looks like this:
VStack {
Text("Title")
Text("Some useful text here.")
}
.padding(10)
This will give us a padding of 10 around all of the edges. In SwiftUI, you can also specify which edges you want padding for and pass in the value like so:
.padding(.horizontal, 10) // Only pads the leading & trailing edges
// OR
.padding([.top, .leading], 10) // Pads top and leading edges only
The syntax is very clean, very readable, and easy to write.
You can actually get the same effect with UIKit, and I'm going to show you how you can do that next.
Padding Stack Views – UIKit
In UIKit the way you would add padding is through a property called layoutMargins and that takes UIEdgeInsets. That would look something like:
stackView.layoutMargins = .init(top: 10, left: 16, bottom: 10, right: 16)
If you ran your code with only the above line added to your stack view, the you wouldn't see any padding. It's very easy to miss or forget the fact that you have to set this ridiculously lengthy named property to true:
stackView.isLayoutMarginsRelativeArrangement = true
We can do better!
Run your project again and you will see padding now. If you're using this a lot, no one wants to write that over and over. So let's clean that up:
Recommended by LinkedIn
extension UIStackView {
var contentLayoutMargins: UIEdgeInsets {
get { layoutMargins }
set {
layoutMargins = newValue
isLayoutMarginsRelativeArrangement = true
}
}
}
What did we do here?
That's it! Now, instead of having to set both of those properties each and every time we want some padding on our stack views, we can just set the one property:
stackView.contentLayoutMargins = .init(top: 10, left: 16, bottom: 10, right: 16)
Much better. However, I'm not a fan of the initializer for UIEdgeInsets. It can be very verbose in a lot of cases. For instance, what if you only wanted to set the leading and trailing padding, or just the bottom? You'd have to fill in all zeros and the padding value for just the bottom, to use the last example. And when you're using this a lot, it starts to take the fun out of things. Therefore....
We can do better!
Builder Patter for UIEdgeInsets — Snippet for you if you'd like!
I recently learned about the builder pattern and thought having a nice edge insets API could be a great use case for it. I'm not going to go into the builder pattern very much. I just wanted to share with you what I did in case you'd like to use it in your own code base. I called my builder: UIEdgeInsetBuilder. Pretty original right? Here's what it looks like:
import UIKit
struct UIEdgeInsetBuilder {
var top: CGFloat = 0
var left: CGFloat = 0
var bottom: CGFloat = 0
var right: CGFloat = 0
func top(_ value: CGFloat) -> Self {
var builder = self
builder.top = value
return builder
}
func left(_ value: CGFloat) -> Self {
var builder = self
builder.left = value
return builder
}
func bottom(_ value: CGFloat) -> Self {
var builder = self
builder.bottom = value
return builder
}
func right(_ value: CGFloat) -> Self {
var builder = self
builder.right = value
return builder
}
func all(_ value: CGFloat) -> Self {
var builder = self
builder.top = value
builder.left = value
builder.bottom = value
builder.right = value
return builder
}
func horizontal(_ value: CGFloat) -> Self {
var builder = self
builder.left = value
builder.right = value
return builder
}
func vertical(_ value: CGFloat) -> Self {
var builder = self
builder.top = value
builder.bottom = value
return builder
}
var insets: UIEdgeInsets {
.init(
top: top,
left: left,
bottom: bottom,
right: right
)
}
}
// MARK: - Extension
extension UIEdgeInsets {
static var set: UIEdgeInsetBuilder { .init() }
}
I thought: "What would I rather it look like when setting insets?" At first, using enums could be a great way, and still would be. I was then inspired by the syntax of the auto layout DSL called SnapKit. And long story short, I ended up using the builder pattern.
All the builder pattern basically does is create a sort of proxy object for you that houses all the possible properties you can set on the very object you're after. In this case having all the edges' insets that we can set. What's great about the builder pattern is that you can use only what you need and never worry about the rest since we can provide default values for all of the properties. If we only wanted to set the bottom padding, we shouldn't have to set all the other edges to 0 at the call site every time we wanna set edge insets. In the end, this is what it looks like when used:
// Example 1
stackView.contentLayoutMargins = .set.bottom(10).insets
// Example 2
stackView.contentLayoutMargins = .set.horizontal(16).insets
// Example 3
stackView.contentLayoutMargins = .set.top(10).left(16).insets
This is much better because we only have to set which edge we need along with the value. I wanted this to read as natural as possible. Usually, instead of .set, you might see .builder(). And instead of insets, you'd see .build(). For this case, I thought turning them into computed properties and naming them set and insets has nice readability.
There are plenty of ways this can be done. This was something I landed on and was fun to explore. Anyway, I hope you learned something or got some use out of this.
Happy coding!!!
Yes, UIStackViews is one of the best techniques for auto layout in iOS. Great article!
The inclusion of the builder pattern is inspired. Nicely done!