2

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)

  • The `body` property isn't optional, and the compiler must be certain it's filled. Similar to how you can't use a top-level `if` statement without an `else` in a view body, you can't use an un-caught `if let` statement alone in the view body (as is in your `MainView` body) in case it fails. I'm not sure if this actually fixes anything or not so apologies – Stoic Mar 08 '23 at 13:40
  • 1
    @Stoic you might be right. I was just hoping it would return an empty view or something. But the main issue was that the view being returned didn't have a concrete type. – Pearse Moloney Mar 10 '23 at 11:17

1 Answers1

2

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)
    }
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313