I've finally started learning SwiftUI by reading "Thinking in SwiftUI" from objc.io. I hadn't even gone a chapter when I realized I didn't understand what the difference was between a property that is a view, a property that is marked as @ViewBuilder and a function that is marked @ViewBuilder.
First, thanks to the book, I understood that if a single View is returned from @ViewBuilder, the ViewBuilder will do nothing and pass the single view back to whoever requested it. For example, the following View's body will be of type `Text`.
If there's more than one view, the @ViewBuilder will build a TupleView, meaning it did flatten the interface into a single view. For example, the following View's body will be of type TupleView<Text, Image>
So, what happens then if we return a property of type View to the body? Well, we can't.
My disconnect was the fact that I didn't understand the difference between using SwiftUI's DSL and using Swift itself. Having learned that, I think I can explain the difference:
- A property that is marked only as View is just that: a view. It is a single view. We could return a view that itself will combine its subviews into a single view (like an HStack) but the property cannot use SwiftUI's DSL. - A property that is marked as @ViewBuilder is allowed to use SwiftUI's DSL in order to reduce the contents of the closure into a single view. This is why View's property body can accept both the Text and an Image and will return a flattened view. - A function that is marked @ViewBuilder is also allowed to use SwiftUI's DSL, will also flatten its views into a single views, but can accept parameters. A function marked as @ViewBuilder and accepts a String as a parameter, would be the equivalent of a new struct View that has a String property. - A @ViewBuilder function will only flatten the elements; it does not decide on the layout of these elements. This is the difference between returning two Views in a body (which will return a TupleView<ViewA, ViewB>) and an HStack with two views in it (which will return an HStack<TupleView<ViewA, ViewB>>.
From this, I gather that separating your Views into smaller views could be done in two ways:
- Creating a new struct that conforms to View - Creating a new function that is @ViewBuilder
I think the difference between these two is that the former is much more reusable than the latter. Basically, if I think the build subview should be used elsewhere in the app, then I should create a new struct for it. If I think this subview is strictly for use inside the struct where the function is declared, then the latter makes more sense.