Home > database >  How to assign value to @State in View from ViewModel?
How to assign value to @State in View from ViewModel?

Time:08-30

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))
  }
}

CodePudding user response:

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

CodePudding user response:

(Ignoring "the best way" part, 'cause it's opinion-based,) one way to achieve that is to make your view model an observable object (which likely already is), adding the publisher of reachedLastPage there, and observe it directly from the view. Something like this:

final class ContentViewModel: ObservableObject {
  @Published var reachedLastPage = false
  init() {
    // Just an example of changing the value.
    DispatchQueue.main.asyncAfter(deadline: .now()   .seconds(1)) { self.reachedLastPage = true }
  }
}

struct ContentView: View {

  var body: some View {
    Text("Hello World")
      .alert(isPresented: $viewModel.reachedLastPage) {
        Alert(title: Text("Alert is triggered"))
      }
  }
  @ObservedObject private var viewModel: ContentViewModel

  init(viewModel: ContentViewModel) {
    self.viewModel = viewModel
  }

}

Once reachedLastPage takes the true value, the alert will be presented.

  • Related