SwiftUI, Passing a View as params in @Viewbuilder
Asked Answered
B

1

6

My curiosity takes me to pass a View type as parameter to @ViewBuilder. Passing a Model/Primitive type as param in @ViewBuilder is perfectly valid.

As shown below code.

struct TestView<Content: View>: View {
    
    let content: (String) -> Content
    
    init(@ViewBuilder content: @escaping (String) -> Content) {
        self.content = content
    }
    
    var body: some View {
        content("Some text")
    }
}

struct ContentTestView: View {
    
    var body: some View {
        TestView {
            Text("\($0)")
        }
    }
}

In place of String in

let content: (String) -> Content

If I try to pass a SwiftUI View type, then Compiler is not happy with it.

let content: (View) -> Content

Even though params for @ViewBuilder accepts custom Protocol type like Searchable but not View protocol.

compiler tell me this Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements

My whole idea is that content can be allowed to hold Section/List/Text in it.

Edit: I expected code like below.

struct TestView<Content: View>: View {

    let content: (View) -> Content

    init(@ViewBuilder content: @escaping (View) -> Content) {
        self.content = content
    }

    var body: some View {
        content(
            List {
                ForEach(0..<10) { i in
                    Text(\(i))
                }
            }
        )
    }
}

struct ContentTestView: View {

    var body: some View {
        TestView { viewbody -> _ in
            
            Section(header: Text("Header goes here")) {
                viewbody
            }
        }
    }
}

Any way can I achieve this ?

Backdrop answered 14/10, 2020 at 11:2 Comment(2)
Very interesting... before proceed... how would you expect to use it in TestView { ??? in, as ??? will be an opaque unknown, for you, view?Pilarpilaster
@Pilarpilaster updated expected code.Backdrop
P
4

The possible solution is to use AnyView, like

struct TestView<Content: View>: View {
    
    let content: (AnyView) -> Content
    
    init(@ViewBuilder content: @escaping (AnyView) -> Content) {
        self.content = content
    }
    
    var body: some View {
        content(AnyView(
            Text("Demo")   // << put here any view hierarchy
        ))
    }
}
Pilarpilaster answered 14/10, 2020 at 11:27 Comment(1)
Thanks for your answer, It works as expected. usage of AnyView is not recommended (allowed in some situation). I was thinking any better approach.Backdrop

© 2022 - 2024 — McMap. All rights reserved.