Search code examples
swiftswiftuicombine

Updating SwiftUI View Based on ViewModel States?


I had a setup using @State in my SwiftUI view and going all my operations in the View (loading API etc) however when attempting to restructure this away from using @ViewBuilder and @State and using a @ObservedObject ViewModel, I lost the ability to dynamically change my view based on the @State variables

My code is now

    
    @ObservedObject private var contentViewModel: ContentViewModel
    
    init(viewModel: ContentViewModel) {
        self.contentViewModel = viewModel
    }
    
    var body: some View {
        if contentViewModel.isLoading {
            loadingView
        }
        else if contentViewModel.fetchError != nil {
            errorView
        }
        else if contentViewModel.movies.isEmpty {
            emptyListView
        } else {
            moviesList
        }
    }

However whenever these viewmodel properties change, the view doesn't update like it did when i used them in the class as @State properties...

ViewModel is as follows:

final class ContentViewModel: ObservableObject {

    var movies: [Movie] = []
    var isLoading: Bool = false
    var fetchError: String?
    
    private let dataLoader: DataLoaderProtocol
    
    init(dataLoader: DataLoaderProtocol = DataLoader()) {
        self.dataLoader = dataLoader

        fetch()
    }

    func fetch() {
        isLoading = true
        dataLoader.loadMovies { [weak self] result, error in
            guard let self = `self` else { return }
            self.isLoading = false
            guard let result = result else {
                return print("api error fetching")
            }
            guard let error = result.errorMessage, error != "" else {
                return self.movies = result.items
            }
            return self.fetchError = error
        }
    }

How can i bind these 3 state deciding properties to View outcomes now they are abstracted away to a viewmodel?

Thanks


Solution

  • Place @Published before all 3 of your properties like so:

     @Published var movies: [Movie] = []
     @Published var isLoading: Bool = false
     @Published var fetchError: String?
    

    You were almost there by making the class conform to ObservableObject but by itself that does nothing. You then need to make sure the updates are sent automatically by using the @Published as I showed above or manually send the objectWillChange.send()

    Edit:

    Also you should know that if you pass that data down to any children you should make the parents property be @StateObject and the children's be ObservedObject