Using @ViewBuilder to create Views which support multiple children
Using the declaration of VStack
we need to use @ViewBuilder
for our content parameter. It is a closure but it shouldn't be @escaping it won't be good to store closure if we need only data from it. I assume that from the Apple declarations.
Also I believe that @inlinable
is important because:
The @inlinable attribute exports the body of a function as part of a module's interface, making it available to the optimizer when referenced from other modules. More info here
struct Layout <Content> : View where Content : View {
var content: Content
@inlinable public init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body : some View {
VStack {
Text("This is a layout")
self.content
}
}
}
To use it:
Layout {
Text("1")
VStack {
Text("1")
Text("2")
}
}
UPD:
As Matteo Pacini noted as a misleading info about @escaping
.
We need to use @escaping
for DynamicViewContent
views.
@escaping
is used Apple's View structures for view structs that are accepting Collections(Array, Range, etc). Because the ForEach
implements DynamicViewContent
- a type of view that generates views from an underlying collection of data. List
in its initializers also ForEach
in Content
public init<Data, RowContent>(_ data: Data, selection: Binding<Selection>?, action: @escaping (Data.Element.IdentifiedValue) -> Void, rowContent: @escaping (Data.Element.IdentifiedValue) -> RowContent) where Content == ForEach<Data, Button<HStack<RowContent>>>, Data : RandomAccessCollection, RowContent : View, Data.Element : Identifiable
Here's an example view that does nothing, just to demonstrate how to use @ViewBuilder
.
struct Passthrough<Content>: View where Content: View {
let content: () -> Content
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}
var body: some View {
content()
}
}
Usage:
Passthrough {
Text("one")
Text("two")
Text("three")
}