Search code examples
swiftswiftuicombine

How to assign value to @State in View from ViewModel?


I have a movie listing view with basic listing functionality, Once pagination reaches to the last page I want to show an alert for that I am using reachedLastPage property.

The viewModel.state is an enum, the case movies has associated value in which there is moreRemaining property which tells if there are more pages or not.

Once the moreRemaining property becomes false I want to make reachedLastPage to true so that I can show an alert.

How can I achieve this in best way?

import SwiftUI
import SwiftUIRefresh

struct MovieListingView<T>: View where T: BaseMoviesListViewModel {
  
  @ObservedObject var viewModel: T
  @State var title: String
  @State var reachedLastPage: Bool = false
  
  var body: some View {
    NavigationView {
      ZStack {
        switch viewModel.state {
        case .loading:
          LoadingView(title: "Loading Movies...")
            .onAppear {
              fetchMovies()
            }
        case .error(let error):
          ErrorView(message: error.localizedDescription, buttonTitle: "Retry") {
            fetchMovies()
          }
        case .noData:
          Text("No data")
            .multilineTextAlignment(.center)
            .font(.system(size: 20))
        case .movies(let data):
          List {
            ForEach(data.movies) { movie in
              NavigationLink(destination: LazyView(MovieDetailView(viewModel: MovieDetailViewModel(id: movie.id)))) {
                MovieViewRow(movie: movie)
                  .onAppear {
                    if movie == data.movies.last && data.moreRemaining {
                      DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                        fetchMovies()
                      }
                    }
                  }
              }
              if movie == data.movies.last && data.moreRemaining {
                HStack {
                  Spacer()
                  ActivityIndicator(isAnimating: .constant(data.moreRemaining))
                  Spacer()
                }
              }
            }
          }.pullToRefresh(isShowing: .constant(data.isRefreshing)) {
            print("Refresheeeee")
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
              refreshMovies()
            }
          }
        }
      }
      .navigationViewStyle(.stack)
      .navigationBarTitle("\(title)", displayMode: .inline)
      .alert(isPresented: $reachedLastPage) {
        Alert(title: Text("You have reached to the end of the list."))
      }
    }
  }
  
  private func fetchMovies() {
    viewModel.trigger(.fetchMovies(false))
  }
  
  private func refreshMovies() {
    viewModel.trigger(.fetchMovies(true))
  }
}


Solution

  • you could try this approach, using .onReceive(...). Add this to your ZStack or NavigationView:

     .onReceive(Just(viewModel.moreRemaining)) { val in
         reachedLastPage = !val
     }
    

    Also add: import Combine