import SwiftUI
struct CardCarousel: View {
var body: some View {
ScrollView(.horizontal) {
LazyHStack(spacing: 12) {
ForEach(0..<8) { index in
RoundedRectangle(cornerRadius: 20)
.fill(.teal.gradient)
.overlay(Text("Card \(index + 1)").bold())
.foregroundStyle(.white)
.containerRelativeFrame(
.horizontal, count: 5, span: 4, spacing: 12)
.frame(height: 200)
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
.contentMargins(.horizontal, 16)
}
}Sizing against the container
containerRelativeFrame answers a question GeometryReader used to monopolize: "make this view a fraction of the container." The simplest form fills an axis:
Image(.banner)
.containerRelativeFrame(.horizontal)
Count, span, spacing
The grid-flavored form divides the container into count equal slots and gives the view span of them, accounting for spacing between slots. It is the idiomatic way to build "one full card plus a peek of the next" carousels, and because it is just sizing, it composes cleanly with scrollTargetBehavior(.viewAligned) snapping.
Custom logic
When fractions are not enough, the closure form receives the container length and axis and returns any length you compute — for example min(length * 0.9, 480) to cap card width on iPad.
.containerRelativeFrame(.horizontal) { length, _ in
min(length * 0.9, 480)
}
Common mistakes
- Expecting an ordinary VStack parent to act as the container — the modifier looks for scroll views, windows, and similar boundaries.
- Mismatched spacing between the stack and the modifier, which breaks slot math and snapping.
- Using it to measure — it sets sizes, it never reports them.
Related reference
Modern ScrollView is configured declaratively: scrollTargetBehavior for paging and snapping, scrollPosition for tracking, contentMargins for insets, plus bounce and disable controls.
GeometryReader hands you the size and frame proposed by the parent so children can adapt. Read frames in .local, .global, or named coordinate spaces — and reach for it sparingly.
Lazy stacks create children only as they scroll into view and support section headers that pin to the edge while their section passes.