How can I return a struct which indirectly inherits the View protocol via another protocol in SwiftUI?

86 Views Asked by At

Suppose I have the following protocol which conforms to View:

protocol Foo: View {
    init(field: Binding<Bool>)
}

I then have two structs which conform to this protocol:

struct BarView: Foo {
    @Binding private var field: Bool
    
    init(field: Binding<Bool>) {
        self._field = field
    }
    
    var body: some View {
        Text(field ? "Some text" : "Some other text")
    }
    
}

struct QuxView: Foo {
    @Binding private var field: Bool
    
    init(field: Binding<Bool>) {
        self._field = field
    }
    
    var body: some View {
        Text(field ? "Some text" : "Some other text")
    }
    
}

Now, in my main view I have a collection of types which conform to Foo. When I try and initialise a view of one of these types, I get the error Type 'any Foo' cannot conform to 'View'. How do I avoid this?

struct MainView: View {
    static let fooViews: [any Foo.Type] = [
        BarView.self,
        QuxView.self
    ]
    @State private var field = false
    
    var body: some View {
        if let fooView = MainView.fooViews.first {
            fooView.init(field: $field)
        }
    }
}

Thanks! (Bear in mind this is a minimal example of the problem I'm trying to solve)

1

There are 1 best solutions below

1
Sweeper On BEST ANSWER

Add an extension to Foo that returns a concrete view type - AnyView.

extension Foo {
    static func create(field: Binding<Bool>) -> AnyView {
        AnyView(Self.init(field: field))
    }
}

and use this extension in the body instead:

if let fooView = MainView.fooViews.first {
    fooView.create(field: $field)
}

Note that if all you need is a factory to create views, consider getting rid of the protocol, and jut using an array of (Binding<Bool>) -> AnyView:

static let fooViewFactories: [(Binding<Bool>) -> AnyView] = [
    { AnyView(BarView(field: $0)) },
    { AnyView(QuxView(field: $0)) },
]

@State private var field = false

var body: some View {
    if let factory = MainView.fooViewFactories.first {
        factory($field)
    }
}