hmlongco / Resolver

Swift Ultralight Dependency Injection / Service Locator framework
MIT License
2.15k stars 190 forks source link

Custom Resolver-container per SwiftUI Scene #152

Closed joethebro2022 closed 2 years ago

joethebro2022 commented 2 years ago

Hi, thank you for making this great repository!

I'm trying to use this with SwiftUI. Essentially, in every SwiftUI Scene, I have this Model that I'm looking to inject into child View-Models. The important thing here is that every Scene (like a window on macOS) has its own Model.

Here's an example:

class Model: ObservableObject {
    @Published var data = ...
}

// The main app file
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

and then in ContentView:

struct ContentView: View {
    @StateObject private var model = Model()

    var body: View {
        // How would I do something similar to this but with Resolver?
        // environmentObject doesn't work with classes
        childViews.environmentObject(model)
    }
}

The idea is that each one of the child View-Models would have a shared Model. But new Scenes would have separate models.

Using Resolver, I can get similar behavior to if I declared Model in App like this:

@main
struct MyApp: App {
    // Model is shared throughout ALL windows
    @StateObject private var model = Model()
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

where one Model is shared throughout multiple different scenes (windows).

However, how would I get Model to only be shared per scene, i.e. initialized for every new window/scene? And then importantly, get access to that in a child view model?

I read the article on scopes, but there doesn't seem to be a way to have a scope like this.

If I use application scope, then the Model lives forever. If I use unique, then if I have multiple child view models in a Scene, then each one of them will get their own brand-new Model which is not what I want. If I use shared scope, the behavior is still the same as application scope.

Is there a way to create a custom Resolver container for each scene? The purpose of using DI in my case would just be to hide Model from the View's. If I decided to pass Model through initializers, then View could technically access it.

I was just looking to see if you had any suggestions. Thanks!

hmlongco commented 2 years ago

In SwiftUI I tend to use state object and environment object as Apple intended. I only use Resolver to inject needed dependencies within a given view model or service.

Btw, don't get the comment, "environmentObject doesn't work with classes". Working with observable objects (classes) is what environment object does.

abhcom commented 2 years ago

Hi, thank you for the reply!

What I mean by "environmentObject doesn't work with classes" is that environmentObject will not inject into classes. It works with and handles classes, but it won't inject an object into say a ViewModel. It only injects into View's. That is what I'm using Resolver for. I apologize for not making that more clear.

However, environmentObject injects into a specific view hierarchy. For example, for every single Scene in a SwiftUI app, I can inject an object, which is created for every new scene. However, from what I've seen, Resolver can't create a new object for every new Scene. It is more on an app-wide basis. (There are scopes and containers, but I think SwiftUI has special behavior to allow it to inject objects into a specific view hierarchy)

Perhaps there is another way? Or is this currently not possible?

I thank you for any advice.

P.S. This is coming from the same person, just a different account.