Skip to content

Building Custom Layout: Spacing (SwiftUI)

A SwiftUI guide for coding agents. Also covers preferred spacing between subviews, subview spacing distance, layout spacing parameter, platform default spacing, flow layout gaps.

A custom Layout is a plain struct, so spacing is just a property. The deeper technique: a nil spacing should fall back to SwiftUI's platform-preferred spacing, which the Subview proxy can compute per pair of views.

Fixed Spacing Is a Property

Add spacing to the layout and include it in both the size calculation and the placement advance.

struct FlowLayout: Layout {
  var spacing: CGFloat = 0
}

// sizeThatFits, when the view stays on the line
lineWidth += cache.sizes[index].width + spacing

// placeSubviews, after placing a view
lineX += cache.sizes[index].width + spacing

// call site
FlowLayout(spacing: 8) { /* views */ }

Preferred Spacing From the Subview Proxy

SwiftUI knows the preferred gap between view kinds (Image next to Text differs from Text next to Text), and the values differ per platform. subview.spacing.distance(to:along:) returns that preferred gap for a pair of neighbors on a given axis.

Cache the pair distances in makeCache: they only change when the subviews change.

struct FlowLayout: Layout {
  var spacing: CGFloat? = nil

  struct Cache {
    var sizes: [CGSize] = []
    var spacing: [CGFloat] = []
  }

  func makeCache(subviews: Subviews) -> Cache {
    let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
    let spacing: [CGFloat] = subviews.indices.map { index in
      guard index != subviews.count - 1 else { return 0 }
      return subviews[index].spacing.distance(
        to: subviews[index + 1].spacing,
        along: .horizontal
      )
    }
    return Cache(sizes: sizes, spacing: spacing)
  }
}

nil Means Preferred, Not Zero

Match the built-in stacks: an explicit value wins, otherwise use the cached preferred distance.

// in both sizeThatFits and placeSubviews
lineX += cache.sizes[index].width + (spacing ?? cache.spacing[index])

With that, FlowLayout { ... } without arguments spaces its children the way an HStack would on the current platform.

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-swiftui-guide({ topic: "swiftui-building-custom-layout-spacing" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems