← All shorts
08iOS 17+2 min read

containerRelativeFrame: Sizing Children as a Fraction of Their Container

Size a view as a percentage of its scroll view or container without a GeometryReader in sight.

An "80%-width card" or a "2-up grid cell" needs to know its container's size before it can compute its own. The classic tool for that, GeometryReader, is greedy — it expands to fill whatever space it's given and forces you to thread sizes back down manually.

#The old way

struct OldRelativeCard: View {
    var body: some View {
        GeometryReader { geometry in
            RoundedRectangle(cornerRadius: 16)
                .fill(.blue.gradient)
                .frame(width: geometry.size.width * 0.8, height: 180)
        }
        .frame(height: 180)
    }
}

That extra .frame(height: 180) on the outside is there only to stop GeometryReader from swallowing the parent layout.

#The new way

import SwiftUI

struct FeaturedProduct: Identifiable {
    let id = UUID()
    let name: String
}

struct FeaturedCarousel: View {
    let products: [FeaturedProduct] = [
        FeaturedProduct(name: "Wireless Headphones"),
        FeaturedProduct(name: "Mechanical Keyboard"),
        FeaturedProduct(name: "Standing Desk"),
        FeaturedProduct(name: "4K Webcam")
    ]

    var body: some View {
        ScrollView(.horizontal) {
            LazyHStack(spacing: 16) {
                ForEach(products) { product in
                    RoundedRectangle(cornerRadius: 20)
                        .fill(.indigo.gradient)
                        .overlay(alignment: .bottomLeading) {
                            Text(product.name)
                                .font(.headline)
                                .foregroundStyle(.white)
                                .padding()
                        }
                        .containerRelativeFrame(.horizontal, count: 5, span: 4, spacing: 16)
                }
            }
            .scrollTargetLayout()
        }
        .scrollTargetBehavior(.viewAligned)
    }
}

count: 5, span: 4 means each card claims four of five equal slices of the container's width — an 80% card with a peek of the next one, computed for you.

#Why it matters

  • No GeometryReader nesting, no manual size propagation, no layout thrashing.
  • The count/span form gives you grid math (2-up, 3-up, 80%-width) without doing the division yourself.
  • Recalculates automatically on rotation, split view resize, and Dynamic Type-driven layout changes.
  • Works equally well inside LazyVGrid cells and plain VStack content, not just horizontal carousels.

#Gotcha

"Container" means the nearest enclosing scroll view (or List), not necessarily the view's immediate parent. If you use .containerRelativeFrame(.horizontal, count: 2) inside a plain HStack that isn't itself inside a ScrollView, the reference size comes from further up the hierarchy — the enclosing scroll view or window — not the HStack's own bounds. Sizes that look right in one layout context can come out wrong after you move the same view into a different parent.

swiftuiios17scrolling

Related shorts