Some Views in SwiftUI, like VStack and HStack support having multiple views as children, like this:
VStack { Text("hello") Text("world") }
From what I gather, they use ViewBuilder to make this possible as explained here.
How can we use @ViewBuilder for creating our own Views which support multiple children? For example, let's say that I want to create a Layout
View which accepts arbitrary children -- something like this:
struct Layout : View { let content: Some View var body : some View { VStack { Text("This is a layout") content() } } }
Any idea how to implement this pattern in SwiftUI?
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") }
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With