0Pricing
Swift Academy · Lesson

The Layout Protocol Basics

Implement sizeThatFits and placeSubviews.

The Layout Protocol Basics is a free Swift Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why a Custom Layout?

SwiftUI ships with HStack, VStack, and ZStack, but sometimes you need arrangement logic they cannot express—radial menus, flow layouts, masonry grids.

The Layout protocol lets you write that logic while still composing with normal SwiftUI views.

The Layout Protocol

Layout requires two methods: sizeThatFits (how big am I?) and placeSubviews (where does each child go?).

You conform a value type and use it like any built-in stack.

struct MyLayout: Layout {
    func sizeThatFits(proposal: ProposedViewSize,
                      subviews: Subviews,
                      cache: inout ()) -> CGSize { .zero }
    func placeSubviews(in bounds: CGRect,
                       proposal: ProposedViewSize,
                       subviews: Subviews,
                       cache: inout ()) { }
}

Using a Custom Layout

A Layout value acts as a container. You pass child views in its trailing closure exactly like a stack.

var body: some View {
    MyLayout {
        Text("One")
        Text("Two")
        Text("Three")
    }
}

ProposedViewSize

The parent proposes a size to your layout via ProposedViewSize. Its width and height are optionals: nil means "unspecified, choose your ideal".

Special values .zero, .infinity, and .unspecified probe minimum, maximum, and ideal sizes.

func sizeThatFits(proposal: ProposedViewSize,
                  subviews: Subviews,
                  cache: inout ()) -> CGSize {
    let width = proposal.width ?? 0
    return CGSize(width: width, height: 44)
}

sizeThatFits in Detail

sizeThatFits returns the size your layout wants given a proposal. SwiftUI may call it multiple times with different proposals to understand your flexibility.

func sizeThatFits(proposal: ProposedViewSize,
                  subviews: Subviews,
                  cache: inout ()) -> CGSize {
    // Sum child ideal sizes for a simple horizontal stack
    let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
    let w = sizes.reduce(0) { $0 + $1.width }
    let h = sizes.map(\.height).max() ?? 0
    return CGSize(width: w, height: h)
}

The Subviews Collection

Subviews is a collection of proxies for each child. Each proxy answers sizeThatFits and exposes layout priorities and custom values.

You never see the real views—only these measurement proxies.

for subview in subviews {
    let size = subview.sizeThatFits(.unspecified)
    print(size)
}

placeSubviews

placeSubviews assigns each child a position inside the bounds rectangle you were given.

Call place(at:anchor:proposal:) on each subview proxy.

func placeSubviews(in bounds: CGRect,
                   proposal: ProposedViewSize,
                   subviews: Subviews,
                   cache: inout ()) {
    var x = bounds.minX
    for subview in subviews {
        let size = subview.sizeThatFits(.unspecified)
        subview.place(at: CGPoint(x: x, y: bounds.midY),
                      anchor: .leading,
                      proposal: .unspecified)
        x += size.width
    }
}

Anchors When Placing

The anchor parameter says which point of the child lands on the position you give. .topLeading places by the top-left corner; .center centers the child on the point.

subview.place(at: CGPoint(x: 0, y: 0),
              anchor: .topLeading,
              proposal: .unspecified)

Bounds Are Not Always at Origin

The bounds rect may not start at (0,0). Always position relative to bounds.minX and bounds.minY, never absolute zero.

let startX = bounds.minX // not 0
let startY = bounds.minY

A Minimal Equal-Spacing Layout

Putting it together, here is a layout that lays children left-to-right with no spacing. The two methods stay consistent: the size reported matches the placement.

struct SimpleHStack: Layout {
    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
        return CGSize(width: sizes.reduce(0){ $0 + $1.width },
                      height: sizes.map(\.height).max() ?? 0)
    }
    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        var x = bounds.minX
        for s in subviews {
            let size = s.sizeThatFits(.unspecified)
            s.place(at: CGPoint(x: x, y: bounds.minY), anchor: .topLeading, proposal: .unspecified)
            x += size.width
        }
    }
}

Consistency Is Your Job

SwiftUI trusts your two methods to agree. If placeSubviews uses more space than sizeThatFits reported, children may clip or overlap.

Keep the geometry calculations shared between both methods.

Quick Check: Layout Basics

Test your grasp of the Layout protocol.

Recap: The Layout Protocol Basics

The Layout protocol turns custom arrangement logic into a reusable container. sizeThatFits reports your size for a ProposedViewSize, and placeSubviews positions each child proxy within bounds using place(at:anchor:proposal:).

Proposals carry optional dimensions, bounds may be offset from origin, and the two methods must stay geometrically consistent. Next we measure subviews in depth.

Frequently asked questions

Is the “The Layout Protocol Basics” lesson free?

Yes — the full text of “The Layout Protocol Basics” is free to read here on the web, and the Swift Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Swift Academy course, upgrade to CoddyKit PRO.

What will I learn in “The Layout Protocol Basics”?

Implement sizeThatFits and placeSubviews. You practise Swift Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Swift Academy?

No prior experience is required. Swift Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “The Layout Protocol Basics” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Swift Academy lesson?

Yes. Every Swift Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. The Layout Protocol Basics
  2. Measuring Subviews
  3. Building a Flow Layout
  4. Layout Cache and Performance
← Back to Swift Academy