0

I'm new in SwiftUI. I'm worked with UIKit and Combine framework building an architecture with ViewModel, UseCases and Repositories. All my architecture is based on the loading states. My loading states are build in this way:

/// Equivalent to @Published with `LoadingState<T, E>` property wrapper
@propertyWrapper public class Loading<T, E: Swift.Error> {
public typealias State = LoadingState<T, E>

public var wrappedValue: State {
    willSet {
        subject.send(newValue)
    }
}

public init(wrappedValue: State) {
    self.wrappedValue = wrappedValue
}

private lazy var subject = CurrentValueSubject<State, Never>(wrappedValue)

public var projectedValue: AnyPublisher<State, Never> {
    return subject.eraseToAnyPublisher()
}
}

and my ViewModel works in this way:

@Loading<MyData, MyError> var myDataLoadingState = .idle
public func getMyData(ID: String) {
    myDataLoadingState = .loading
    
    myDataUseCase.execute(ID: ID)
        .receive(on: DispatchQueue.main)
        .sink { completion in
            guard case .failure(let error) = completion else { return }
            myDataLoadingState = .failure(error)
        } receiveValue: { myData in
            self. myDataLoadingState = .success(myData)
        }
        .store(in: &self.cancellables)
}

The controller works in this way:

viewModel.$myDataLoadingState
       .sink { state in
           switch state {
           case .idle:
            break
           case .loading:
             self.showLoader()
           case .success(let myData):
            print(myData)
           case .failure(let error):
            self.print(error)
            self.hideLoader()
           }
       }
       .store(in: &cancellables)

Can I use the loading state and my ViewModel in SwiftUI? I tried in this way but seems not works:

struct ContentView: View {
@StateObject var viewModel: MyViewModel

var body: some View {
    switch viewModel.loadingState {
    case .idle:
        Text("Idle")
    case .loading:
        Text("Loading")
    case .success(let myData):
        Text(myData.name)
    case .failure(let error):
        Text(error.localizedDescription)
    }
}
}

The ViewModel now is an ObservableObject

public class MyViewModel: ObservableObject {

}

Thanks in advance

1 Answers1

0

Just recognised that you already has property wrapper for myDataLoadingState, so not sure if you are allowed to make it @Published instead. Anyway to inform @StateObject wrapper (who is a listener) you should fire event about changes in view model. A possible way is to use objectWillChange directly, like

myDataUseCase.execute(ID: ID)
    .receive(on: DispatchQueue.main)
    .sink { completion in
        guard case .failure(let error) = completion else { return }

        self.objectWillChange.send()             // << here !!
        myDataLoadingState = .failure(error)
    } receiveValue: { myData in
        self.objectWillChange.send()             // << here !!
        self.myDataLoadingState = .success(myData)
    }
    .store(in: &self.cancellables)
Asperi
  • 228,894
  • 20
  • 464
  • 690