SwiftUI.cc
Snippets
LayoutAdaptive LayoutIntermediate

SwiftUI containerRelativeFrame Sizing

containerRelativeFrame sizes a view as a fraction of its nearest container — full width, one-of-three columns, or custom math — without GeometryReader.

4 min readUpdated 2026-06
Peeking carousel with count and span
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)
    }
}

containerRelativeFrame preview

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