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