1

I am attempting to implement Dependency Injection with a SwiftUI View by assigning the Type of a View to a static var that conforms to a CustomViewInterface protocol.

static var MyCustomView: any CustomViewInterface.Type = CustomView.self

The goal being to inject any View that conforms to that protocol and initialize it inside the receiving class.

When I initialize the custom SwiftUI View inside the body block of the ContentView I get the error

Type 'any CustomViewInterface' cannot conform to 'View'.

Although when I print out the type(of: of the View Type it is CustomView (not CustomViewInterface) I don't understand why the Type is different when it's initialized inside the body block of the ContentView vs in the initializer of the DefaultComponentImplementation class.

My question is, how can I initialize a custom SwiftUI View from a static var that conforms to a protocol. The end goal is to use Dependency Injection for SwiftUI Views.

import SwiftUI

struct ContentView: View {
    let defaultComponenetImplementaion = DefaultComponentImplementation()

    var body: some View {
        VStack {
            CustomView(title: "cat") // this works
            DefaultComponentImplementation.MyCustomView.init(title: "mouse") // error: Type 'any CustomViewInterface' cannot conform to 'View'
        }
    }
}

protocol CustomViewInterface: View {
    var title: String { get set }

    init(title: String)
}

struct CustomView: CustomViewInterface {
    var title: String

    var body: some View {
        Text(title)
    }
}

class DefaultComponentImplementation {
    static var MyCustomView: any CustomViewInterface.Type = CustomView.self
    
    init() {
        print(type(of: DefaultComponentImplementation.MyCustomView.init(title: "cat"))) // prints CustomView
    }
}
Nate Potter
  • 3,222
  • 2
  • 22
  • 24

1 Answers1

0

No, you cannot use any View, or in this case any CustomViewInterface, in a view builder. any CustomViewInterface does not conform to View. The reason for this is a rather long story. It basically boils down to the fact that View has certain static requirements that an existential type does not possess, like the Body associated type. (any CustomViewInterface).Body is not a thing.

Since you want to show different kinds of views depending on a metatype property, erasing them to AnyView (which unlike any View, is a concrete type) should work.

Add an extension to erase any CustomViewInterface to AnyView:

extension CustomViewInterface{
    func toAnyView() -> AnyView {
        AnyView(self)
    }
}

Then

DefaultComponentImplementation.MyCustomView.init(title: "mouse").toAnyView()
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • That's great, it does work! However, I am still confused about when the compiler knows what the concrete type is. I would expect that after the initialization `DefaultComponentImplementation.MyCustomView.init(title: "mouse")` the concrete type is known? Then that type is erase with `.toAnyView()` Can you explain why the `body` block interprets that line as a protocol until `toAnyView()` is called? – Nate Potter May 04 '23 at 09:48
  • 1
    @NatePotter You declared `MyCustomView` to be of type `any CustomViewInterface.Type`, so calling `init` on that creates a `any CustomViewInterface`. The compiler only knows the *declared* type and only uses the declared type to analyse your code. – Sweeper May 04 '23 at 09:51