1

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.

1 Answers1

1

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.

pawello2222
  • 46,897
  • 22
  • 145
  • 209
  • Thanks for your answer. First solution partly works, but thing is, when I update some object in array, view doesnt change unless I add new item to array. – Alexey Kolchedanstev Jun 06 '20 at 13:13
  • Yes, that's because you observe `places` and not it's items. If you change an item, the `places` variable remains unchanged. It's the same as the problem with the `DIContainer` which I explained above. – pawello2222 Jun 06 '20 at 13:47
  • I honestly cant get it. I did exactly how you showed in first solution above. Also I added `self.objectWillChange.send()` to `willSet` of my `places` in userData class. What should I do to observe `places` and not it's items?? – Alexey Kolchedanstev Jun 06 '20 at 14:49
  • 1
    One of the solutions is to make `Place` a *struct* instead of a *class*. You can find more detailed explanation in the answer to this question: [Why an ObservedObject array is not updated in my SwiftUI application?](https://stackoverflow.com/questions/57459727/why-an-observedobject-array-is-not-updated-in-my-swiftui-application) – pawello2222 Jun 06 '20 at 17:39