Alexey Kolchedanstev
Alexey Kolchedanstev

Reputation: 122

SwiftUI cant bind injected value

I found this demo project that shows how to use clean swift with swiftU, so I tried to do something like this on my own. I came up with problem while using Core Data. As clean swift architecture only one source (appState in my case) can pass data to view. So I created injection container, it has interactor and appState. Container:

struct DIContainer: EnvironmentKey {

    @ObservedObject var appState: AppState
    let interactors: Interactors

    init(appState: AppState, interactors: Interactors) {
        self.appState = appState
        self.interactors = interactors
    }
class AppState: ObservableObject, Equatable {
    var userData = UserData()
}

extension AppState {
    class UserData: ObservableObject, Equatable {
        @Published var places:[Place] = []
}

And I inject enviroment like this:

@Environment(\.injected) private var injected: DIContainer

And finally view that uses it:

var body: some View {
        NavigationView {
            ZStack {
                List {
                    ForEach(injected.$appState.userData.places.wrappedValue){ place in
                        PlaceRow(place: place)
                    }
....

Problem is that even array is loade, view doesnt react to that. It refreshes only when i navigate to other view. Also, when I delete or add new elements to array, view has no changes. If you can give me some ideas it would be nice. I googled for 4 days, everything I find is not working for me.

Upvotes: 1

Views: 246

Answers (1)

pawello2222
pawello2222

Reputation: 54426

If you're injecting the DIContainer into your View like this:

@Environment(\.injected) private var injected: DIContainer

remember that your View will not refresh when the DIContainer properties will change. You have to manually observe them.

You can try one of these solutions:

1) Subscribe to the AppState properties' changes in .onReceive:

@Environment(\.injected) private var injected: DIContainer

var body: some View {
    NavigationView { ... }
        .onReceive(injected.appState.userData.$places) { places in
            // do something with places, 
            // eg. assign to a @State variable to be used in a `ForEach` loop...
        }
}

2) Inject the UserData directly as an @EnvironmentObject:

@EnvironmentObject var userData: UserData

ContentView().environmentObject(container.appState.userData)

As the UserData is an ObservableObject it will change whenever any of its @Published properties (eg. places) is modified.

3) Subscribe to the AppState/UserData in the ViewModel and observe the only the ViewModel in your View.

The more detailed explanation can be found in this version of the same demo project you linked in your question.

Upvotes: 1

Related Questions