SwiftUI: @ViewBuilder can be applied to body?

While SwiftUI is somewhat off topic here, I've run into what I think is a fundamental language question. I've recently stumbled across a way to make SwiftUI body properties more dynamic, and I'm not quite sure how it works at all. Say I have a view and I'd like to make the body dynamic:

struct DynamicView: View {
    let condition: Bool
    
    var body: some View {
        if condition {
            return Text("Text")
        } else {
            return Rectangle()
        }
    }
}

Of course this fails to compile due to not returning the same type through the opaque generic. However, adding @ViewBuilder to body lets it compile and, perhaps more shockingly, appears to work correctly.

struct DynamicView: View {
    let condition: Bool
    
    @ViewBuilder
    var body: some View {
        if condition {
            return Text("Text")
        } else {
            return Rectangle()
        }
    }
}

I thought @_functionBuilder types had to be applied to, you know, functions. All I can think of is that it's somehow being applied to body's getter, which I didn't think was possible.

1 Like

@ViewBuilder effectively converts your body to this:

var body: some View {
    if condition {
        return _ConditionalContent<Text, Rectangle>(first: Text("Text"))
    } else {
        return _ConditionalContent<Text, Rectangle>(second: Rectangle())
    }
}

So both branches return a value of type _ConditionalContent<Text, Rectangle>.

You cannot do this transformation manually because _ConditionalContent has internal visibility.

You can use Group instead of the @ViewBuilder annotation:

var body: some View {
    Group {
        if condition {
            Text("Text")
        } else {
            Rectangle()
        }
    }
}
2 Likes

Iā€™m familiar with the mechanics of how @ViewBuilder works. My question was more about how the builder is being applied to a property which is not a function.

Oh. Well, it's in the proposal:

A function builder type can be used as an attribute in two different syntactic positions. The first position is on a func , var , or subscript declaration. For the var and subscript , the declaration must define a getter, and the attribute is treated as if it were an attribute on that getter. The func , var , or subscript may not be a protocol requirement or any other sort of declaration which does not define an implementation. A function builder attribute used in this way causes the function builder transform to be applied to the body of the function; it is not considered part of the interface of the function and does not affect its ABI.

1 Like