SwiftUI - Can I define an empty initializer for a generic view?

Viewed 903

I have a generic view with an optional @ViewBuilder. I want to have two initializers, one is responsible for setting the @ViewBuilder and another which should serve as an empty default.

struct OptionalViewBuilder<Content: View>: View {
    
    let content: (() -> Content)?
    
    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }
    
    init() {
        self.content = nil
    }
    
    var body: some View {
        VStack {
            Text("Headline")
            Divider()
            content?()
        }
    }
}

If I initialize the view with the ViewBuilder parameter, it works of course. However, when I use the empty initializer, I get the error message:

Generic parameter 'Content' could not be inferred

struct ContentView: View {
    var body: some View {
        OptionalViewBuilder {
            Text("Text1")
            Text("Text2")
        }
        OptionalViewBuilder() // <-- Generic parameter 'Content' could not be inferred
    }
}

I know that Swift needs explicit types at runtime, but is there a way to make the default initializer work anyway?

2 Answers

Because OptionalViewBuilder is generic with respect to its Content type, you'd need to define that type as something in your default case. This could be EmptyView, if you don't want to render anything.

To do that, you'd need an init which is constrained to Content == EmptyView, and the content property need not be optional:

struct OptionalViewBuilder<Content: View>: View {
    
    let content: () -> Content
    
    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }
    
    init() where Content == EmptyView {
        self.init(content: { EmptyView() })
    }
    
    var body: some View {
        VStack {
            Text("Headline")
            Divider()
            content()
        }
    }
}

I can make the compile error go away by adding an empty closure, like this:

struct ContentView: View {
    var body: some View {
        OptionalViewBuilder {
            Text("Text1")
            Text("Text2")
        }
        OptionalViewBuilder() {} // I added an empty closure
    }
}

Does this get you what you need?

Related