Skip to content

Container Views: Basics (SwiftUI)

A SwiftUI guide for coding agents. Also covers custom container view, foreach subviews, group subviews, subviewscollection, carousel from viewbuilder content, decompose child views.

A container view holds views the caller supplies through a @ViewBuilder closure. The iOS 18 era decomposition APIs, ForEach(subviews:) and Group(subviews:), let the container pull those children apart and lay them out its own way.

Plain containers with @ViewBuilder

The classic form wraps the content without touching its children. This is enough when the container only adds chrome around the whole group.

struct Card<Content: View>: View {
  @ViewBuilder var content: Content

  var body: some View {
    VStack { content }
      .padding()
      .background(Material.regular, in: .rect(cornerRadius: 8))
      .shadow(radius: 4)
  }
}

Iterate children with ForEach(subviews:)

ForEach(subviews:) extracts the children of a content view and hands each one to you as a Subview. A Subview conforms to View, so modifiers still attach, and it carries a stable id plus its container values.

struct Carousel<Content: View>: View {
  @ViewBuilder var content: Content

  var body: some View {
    ScrollView(.horizontal) {
      LazyHStack {
        ForEach(subviews: content) { subview in
          subview.containerRelativeFrame(.horizontal)
        }
      }
      .scrollTargetLayout()
    }
    .scrollTargetBehavior(.viewAligned)
    .contentMargins(16)
  }
}

Index children with Group(subviews:)

Group(subviews:) gives the whole SubviewsCollection at once. It conforms to RandomAccessCollection, so you can treat the first child differently and slice the rest.

struct Magazine<Content: View>: View {
  @ViewBuilder var content: Content

  var body: some View {
    ScrollView {
      Group(subviews: content) { subviews in
        if !subviews.isEmpty {
          subviews[0]
            .containerRelativeFrame(.vertical) { length, _ in length / 3 }
        }
        if subviews.count > 1 {
          ScrollView(.horizontal) {
            LazyHStack {
              ForEach(subviews[1...], id: \.id) { $0.containerRelativeFrame([.horizontal, .vertical]) }
            }
            .scrollTargetLayout()
          }
          .scrollTargetBehavior(.viewAligned)
        }
      }
    }
  }
}

Guard the indexed access: check isEmpty and count before subscripting, because the caller decides how many children exist.

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-mastering-container-views-basics" })
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