How to use protocols to support managing SwiftUI views from different modules ?

In out project, we are creating a modular architecture where each module conform to certain protocol requirements for displaying the UI.

For example:

public protocol ModuleProviding: Sendable {
    associatedtype Content: View

    /// Creates and returns the main entry point view for this module
    /// - Parameter completion: Optional closure to be called when the flow completes
    /// - Returns: The main view for this module
    @MainActor 
    func createView(_ completion: (() -> Void)?) -> Content
}

This protocol can be implemented by all different modules in the app, and I use a ViewProvider structure to build the UI from the module provided:

public struct ViewProvider: Equatable {
    let id = UUID()
    let provider: any ModuleProviding
    let completion: () -> Void
    
    public init(provider: any ModuleProviding, completion: @escaping () -> Void) {
        self.provider = provider
        self.completion = completion
    }
    
    @MainActor
    public func layoutUI() -> some View {
        provider.createView(completion)
    }

This code throws an error:

Type 'any View' cannot conform to 'View'

To solve this error, there are two ways, one is to wrap it in AnyView, which I don't want to do.

The other option is to type check the provider with its concrete type:

@ViewBuilder @MainActor
    private func buildViewForProvider(_ provider: any ModuleProviding, completion: (() -> Void)?) -> some View {
        switch provider {
        case let p as LoginProvider:
            p.createView(completion)
        case let p as PostAuthViewProvider:
            p.createView(completion)
        case let p as OnboardingProvider:
            p.createView(completion)
        case let p as RewardsProvider:
            p.createView(completion)
        case let p as SplashScreenProvider:
            p.createView(completion)
        default:
            EmptyView()
        }
    }

This approach worked, but it defeats the purpose of using protocols and it is not scalable anymore.

Are there any other approaches I can look at ? Or is this limitation in SwiftUI ?

What about wrapping the provider view in a group view?

Could you try using generics?

public struct ViewProvider<Provider: ModuleProviding>: Equatable {
    let provider: Provider
    
    public init(provider: Provider, completion: @escaping () -> Void) {
        ...
    }
}


Since I don't have any other knowledge about the rest of your codebase, I can't be certain whether this will fit in or even work properly.

appreciate your response @BabyJ , unfortunately I cannot do that since here is how I setup my app:

struct LaunchApp: App {
    @State private var appModel = AppModel()
    
     var body: some Scene {
        WindowGroup {
            appModel.viewProvider.layoutUI()
        }
    }
}

and in my AppModel, the view provider is a property that can be changed based on what module user is currently on.

If I make the ViewProvider generic as you suggested, then I have to specify those requirements in my app model layer which is not possible afaik.

@Observable @MainActor
final class AppModel {
      var viewProvider: ViewProvider
      
      init() {
        let defaultProvider = SplashScreenProvider()
        self.viewProvider = ViewProvider(provider: defaultProvider, completion: {})
        setupFlowCoordinator()
      }

      func setupFlowCoordinator() {
        flowCoordinator = FlowCoordinator(registry: appCoordinator.registry, configuration: config) { provider in
            self.viewProvider = provider
        }
    }

during the app session, the setupFlowCoordinator method will be updating the view provider depending on what we want to show to the user, it could be onboarding/ new feature flows etc.

How to use protocols to support managing SwiftUI views from different modules ?
 
 
Q