Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using @ViewBuilder to create Views which support multiple children

Tags:

swift

swiftui

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?

like image 727
bento Avatar asked Jun 10 '19 19:06

bento


2 Answers

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") } 
like image 123
Matteo Pacini Avatar answered Nov 13 '22 13:11

Matteo Pacini


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 
like image 30
DenFav Avatar answered Nov 13 '22 14:11

DenFav