1

I have 3 classes:

class ClassOne: ObservableObject {
    @Published var loading: Bool = false
}

class ClassTwo: ObservableObject {
    @Published var loading: Bool = false
}

class ClassThree: ObservableObject {
    @Published var loading: Bool = false
}

In a SwiftUI view I need to do something when all loading variables are true

This is a simplified version of my files of course: loading var of every class is set true or false by a download method. I just need something to check if all download are completed and remove the loading view.

struct MainScreen3: View {
    @State private var cancellables = Set<AnyCancellable>()
    @EnvironmentObject var classOne: ClassOne
    @EnvironmentObject var classTwo: ClassTwo
    @EnvironmentObject var classThree: ClassThree
    
    @State private var loading: Bool = true
    
    var body: some View {
        VStack {
            if loading {
                Text("Please wait...")
            } else {
                Text("Done!")
            }
            
        }.onAppear {
            self.classOne.fetchFromServer()
            self.classTwo.fetchFromServer()
            self.classThree.fetchFromServer()
        }
    }
}

Any suggestion?

KScamorza
  • 13
  • 2

2 Answers2

3

You can use the computed property.

private var loading: Bool {
   (self.classOne.loading || self.classTwo.loading || self.classThree.loading)
}
Raja Kishan
  • 16,767
  • 2
  • 26
  • 52
  • Indeed a good answer but I this is computed only when view is loaded first time. I need something that update the view when classes's `loading` changes (and it changes after download finished) – KScamorza May 10 '21 at 15:38
  • Since from the all API any single parameter is used in view so if the var is updated then it will automatically change. – Raja Kishan May 10 '21 at 15:39
  • 1
    “this is computed only when view is loaded first time”—incorrect. SwiftUI knows when you access a property of an `EnvironmentObject` like `classOne` from your `body` method. It remembers that it needs to call `body` again if `classOne`'s `objectWillChange` publisher fires. Same for `classTwo` and `classThree`. – rob mayoff May 10 '21 at 16:27
  • However the body of this computed `loading` property is wrong, as it returns `true` if and only if **none** of the classes are loading. Instead it should be something like `private var loading: Bool { classOne.loading || classTwo.loading || classThree.loading }`. – rob mayoff May 10 '21 at 16:29
  • But think one time if call all API it will use sure in the view so if any one of the class var is sure to publish and view will update. – Raja Kishan May 10 '21 at 16:30
  • 1
    @robmayoff Thanks for explanation: I missed the point that SwiftUI knows when you access a property of an`EnvironmentObject` like classOne from`body` so for my question this is another valid solution. – KScamorza May 11 '21 at 06:08
1

You can use combineLatest to combine all 3 loading values into a single Publisher. You can subscribe to this publisher using onReceive on the view and update the existing loading State property to trigger a UI update.

.onReceive(classOne.$loading.combineLatest(classTwo.$loading, classThree.$loading, { $0 && $1 && $2 })) { loading in
    self.loading = loading
}
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
  • Yes, this is what I tried to do. My example is simplier than the real code. With only 3 published value this is correct but in my real scenario I have 8 "loading" classes...how your solution can be applied with so many variables? – KScamorza May 10 '21 at 15:36
  • @KScamorza you need to implement a `CombineLatestMany` publisher, similar to `MergeMany`. [Here's](https://github.com/CombineCommunity/CombineExt/blob/main/Sources/Operators/CombineLatestMany.swift) a possible implementation for that. – Dávid Pásztor May 10 '21 at 15:41
  • 1
    Works flawlessly! Thanks for your suggestion @Dávid Pásztor – KScamorza May 10 '21 at 16:03