March 19th, 2026

That curious Layout behavior

There’s a fascinating behavior in SwiftUI with the Layout protocol.

A Layout is not a View. When you create a Layout, you size and place views, but you don’t declare a body. And yet, it’s perfectly possible to use an instance of a Layout directly within the view hierarchy.

struct MyVStack: Layout {
    func sizeThatFits(
        proposal: ProposedViewSize,
        subviews: Subviews,
        cache: inout ()
    ) -> CGSize { /* ... */ }
 
    func placeSubviews(
        in bounds: CGRect,
        proposal: ProposedViewSize,
        subviews: Subviews,
        cache: inout ()
    ) { /* ... */ }
}
 
struct MyView: View {
    var body: some View {
        MyVStack {
            Text("1")
            Text("2")
        }
    }
}

If a Layout isn’t a view, it must produce a view responsible for sizing and placing its child views, as described in our Layout implementation.

// SwiftUI side
extension Layout {
    func makeView<V: View>(
        @ViewBuilder _ content: () -> V
    ) -> some View { /* ... */ }
}
 
MyVStack().makeView {
    Text("A")
    Text("B")
}

But in real SwiftUI, we’re not calling any such function. We’re simply creating an instance using our initializer.

// In SwiftUI, we don't call a function, we create an instance
MyVStack {
    Text("A")
    Text("B")
}

Wait, initializer? Where does this closure in the Layout’s initializer come from? SwiftUI is full of clever uses of Swift’s features, and this case is no exception.

You probably didn’t hear much about it when it was introduced with Swift 5.2, but there’s callAsFunction. It lets you call an instance of a type (like a struct or class) as if it were a function—provided that type implements a method named callAsFunction.

As we mentioned earlier, a Layout isn’t a view. So, it needs to be converted into one. Instead of calling makeView, SwiftUI uses callAsFunction.

extension Layout {
    func callAsFunction<V: View>(
        @ViewBuilder _ content: () -> V
    ) -> some View { /* ... */ }
}

Which, on our side, becomes:

MyVStack().callAsFunction {
    Text("A")
    Text("B")
}

The callAsFunction eliminates the boilerplate of naming the function. Swift even allows us to synthesize this:

// Parentheses become optional
MyVStack {
    Text("A")
    Text("B")
}

At first glance, this might seem like pure SwiftUI magic. But really, it removes a lot of boilerplate and lets us focus on what matters: sizing and placing views. There’s no magic here, just a clever use of Swift’s tools.

looping arrowfollow me here!