Exploring SwiftUI: HStack and VStack with Zero Spacing

In SwiftUI, VStack and HStack components help in arranging views vertically and horizontally. However, their default spacing can vary across different operating systems, catering to each platform's specific needs.

Imagine a scenario where a designer hands you a design with specific spacing requirements for each view. Constantly using VStack(spacing: 0) { } or HStack(spacing: 0) { } can quickly become a bit tedious for hundreds of such views.

You can create custom views that provide zero spacing by default, effectively serving as syntactic sugar SwiftUI views. Let's call these custom views ZeroSpacingVStack and ZeroSpacingHStack.

We will explore how to create these custom views, along with examples to demonstrate their usage. Time to take control of your view spacing!

ZeroSpacingVStack

This custom view follows a structure similar to SwiftUI's VStack, allowing you to specify the horizontal alignment and accept a view builder as a parameter:

struct ZeroSpacingVStack<Content>: View where Content: View {
  private let viewBuilder: Content
  private let horizontalAlignment: HorizontalAlignment

  init(_ horizontalAlignment: HorizontalAlignment = .center, @ViewBuilder content: () -> Content) {
    self.horizontalAlignment = horizontalAlignment
    self.viewBuilder = content()
  }

  var body: some View {
    VStack(alignment: horizontalAlignment, spacing: 0) {
      viewBuilder
  }
}

To use ZeroSpacingVStack, simply wrap your views inside it, just like you would with a regular VStack. By default, the horizontal alignment is set to .center. Here's an example usage:

ZeroSpacingVStack {
  Text(track.title)
    .font(.headline)
    .padding(.bottom, 8)
  
  Text(track.artistName)
    .foregroundColor(.secondary)
    .padding(.bottom, 4)
}

In this example, ZeroSpacingVStack arranges the track title and artist name vertically with zero spacing between them. You can then apply specific .padding() as required.

If you want to specify a different horizontal alignment, pass it as a parameter to ZeroSpacingVStack. For instance:

ZeroSpacingVStack(.leading) {
  Text(title)
    .font(type: .montserrat, weight: .regular, size: 14)
    .padding([.leading, .bottom], 4)
  
  Text(subtitle)
    .font(type: .montserrat, weight: .light, size: 12)
}

In this case, the views inside ZeroSpacingVStack will be aligned to the leading edge.

ZeroSpacingHStack

Similar to ZeroSpacingVStack, the ZeroSpacingHStack custom view follows the structure of SwiftUI's HStack. It allows you to specify the vertical alignment and accept a view builder as a parameter:

struct ZeroSpacingHStack<Content>: View where Content: View {
  private let viewBuilder: Content
  private let verticalAlignment: VerticalAlignment
  
  init(_ verticalAlignment: VerticalAlignment = .center, @ViewBuilder content: () -> Content) {
    self.verticalAlignment = verticalAlignment
    self.viewBuilder = content()
  }
  
  var body: some View {
    HStack(alignment: verticalAlignment, spacing: 0) {
      viewBuilder
    }
  }
}

To use ZeroSpacingHStack, wrap your views inside it, just like you would with a regular HStack. By default, the vertical alignment is set to .center. Here's an example usage:

ZeroSpacingHStack {
  Text(track.title)
    .font(.headline)
    .padding([.leading, .bottom], 8)
  
  Spacer()
  
  Text(track.rating)
    .foregroundColor(.secondary)
    .padding(.trailing, 8)
    .padding(.bottom, 4)
}

In this example, ZeroSpacingHStack arranges the track title and rating horizontally with zero spacing between them. The Spacer() pushes the rating view to the trailing edge. You can then apply specific .padding() as required.

To specify a different vertical alignment, pass it as a parameter to ZeroSpacingHStack. For instance:

ZeroSpacingHStack(.top) {
  WebImage(url: track.artwork.url)
    .resizable()
    .indicator(.activity)
    .padding(.leading, 4)
  
  Text(track.title)
    .font(type: .montserrat, weight: .bold, size: 14)
}

In this case, the views inside ZeroSpacingHStack will be aligned to the top.

Conclusion

While these custom views might seem like a lazy approach, they are useful when working on projects with hundreds of views. ZeroSpacingVStack and ZeroSpacingHStack provide a consistent way to handle zero spacing across your app.

If you have a better approach or any suggestions to improve these custom views, feel free to reach out to me on Twitter at @rudrankriyam. I am always open to constructive feedback and appreciate any insights you might have.

Thank you for taking the time to read this article. I hope you found it helpful and enjoyable. Happy coding!