Padding and UIStackViews
Image from: https://edu.gcfglobal.org/en/basic-css/padding-in-css/1/

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:

extension UIStackView {
    var contentLayoutMargins: UIEdgeInsets {
        get { layoutMargins }
        set {
            layoutMargins = newValue
            isLayoutMarginsRelativeArrangement = true
        }
    }
}        

What did we do here?

  1. We've created our own version of the property layoutMargins in an extension on UIStackView that does a little extra work for us. It is of the same type: UIEdgeInsets
  2. We create a getter that just passes back the layoutMargins value
  3. We create a setter that takes in the newValue — UIEdgeInsets — and sets the layoutMargins property while also setting isLayoutMarginsRelativeArrangement to true for us

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!

To view or add a comment, sign in

Others also viewed

Explore content categories