skip to Main Content

I’m new to MVVM and i am trying to pass a location data from my ContenView to DetailsView’s viewModel which is DetailsViewViewModel.

My Opening View -> ContentView (My data is here)

Second View -> DetailsView

Data must be reach -> DetailsViewViewModel

Here is my sheet in ContentView

.sheet(item: $viewModel.selectedPlace) { place in
            DetailsView(location: place) { newLocation in
                viewModel.updateLocation(location: newLocation)
            }

I know i’m trying to send my data to details view and it’s wrong. It was like that before i convert the architecture to the MVVM and this is the only place that i couldn’t convert.
Also here is my DetailsViewViewModel

extension DetailsView {
@MainActor class DetailsViewViewModel: ObservableObject {
    enum LoadingState {
        case loading, loaded, failed
    }
    var location: Location
    @Published var name: String
    @Published var description: String
    @Published var loadingState = LoadingState.loading
    @Published var pages = [Page]()
    init() {
        self.location =  // ??? how should i initialize?
        self.name = location.name
        self.description = location.description
    }

What is the proper way to this. Using another views data in another views viewmodel.

4

Answers


  1. @StateObject var viewModel = ViewModel()
    
    struct ParentView: View {
        
        var body: some View {
            Button(action: {
                
            }, label: {
                Text("btn")
            })
                .sheet(item: $viewModel.selectedPlace) { place in
                    DetailView(name: place.name,
                               location: place.location,
                               description: place.description)
                }
        }
    }
    
    struct DetailView: View {
        
        var name: String
        var location: String
        var description: String
        
        var body: some View {
            VStack {
                Text(name)
                Text(location)
                Text(description)
            }
        }
    }
    
    Login or Signup to reply.
  2. since location data is your business layer data, you need a use-case to provide it to both view models, and to optimize it caching the response is the way to go.

    -ViewModel is responsible to hold the latest view states and data

    -The domain layer is responsible to handle business logic

    -The data layer (networking, cache, persistence, or in-memory) is responsible for providing the most efficient data storage/retrieval solutions

    So, if you are okay with these defenitions and think of writing test for these view models you know that it is not right to inject data from another ViewModel because you would not test that view model on making sure it passes the data to the next viewModel and it is not its responsibility, but you write many tests for you data layer to make sure service calls and caching systems are working properly.

    Login or Signup to reply.
  3. Let me try to put in an example that uses the convenience of @EnvironmentObject:

    1. Your view model is a class that conforms to ObservableObject, so you can have those nice variables @Published that change the state of the views.

    2. Your main view – or also your App – must "own" the view model, meaning it needs to create the one and only instance of your view model that will be used by all views.

    3. You pass the view model from one view to another using @StateObject and @ObservableObject, but in this example I prefer to use another approach. Make your main view inject the instance of your view model in the environment, so all other views will read from that. The main view uses .environmentObject(viewModel) to do that.

    4. The other views read the view model from the environment by calling @EnvironmentObject. They create a variable specifying only the type – there can only be one instance per type in the environment.

    This is the way with which all view will read from the same model. See below a functioning example:

    Step 1:

    class MyViewModel: ObservableObject {
        
        @Published private(set) var color: Color = .blue
        
        @Published var showSheet = false
        
        func changeColorTo(_ color: Color) {
            self.color  = color
        }
        
    }
    

    Steps 2 and 3:

    struct Example: View {
        
        @StateObject private var viewModel = MyViewModel()   // Here is the step (2)
        
        var body: some View {
            OneView()
                .environmentObject(viewModel)    // Here is the step (3)
        }
    }
    

    Step 4 in two different views:

    struct OneView: View {
        @EnvironmentObject var viewModel: MyViewModel    // Here is step (4)
        
        var body: some View {
            VStack {
                Text("Current color")
                    .padding()
                    .background(viewModel.color)
                
                Button {
                    if viewModel.color == .blue {
                        viewModel.changeColorTo(.yellow)
                    } else {
                        viewModel.changeColorTo(.blue)
                    }
                } label: {
                    Text("Change color")
                }
    
                Button {
                    viewModel.showSheet.toggle()
                } label: {
                    Text("Now, show a sheet")
                }
                .padding()
            }
            
            .sheet(isPresented: $viewModel.showSheet) {
                DetailView()
            }
        }
    }
    
    struct DetailView: View {
        @EnvironmentObject var viewModel: MyViewModel    // Here is step (4)
        
        var body: some View {
            VStack {
                Text("The sheet is showing")
                    .padding()
                
                Button {
                    viewModel.showSheet.toggle()
                } label: {
                    Text("Now, stop showing the sheet")
                }
            }
        }
    }
    
    Login or Signup to reply.
  4. You need to initialise DetailsViewModel from ContentView sheet when you are adding the DetailsView like below:

    ContentView

    struct ContentView: View {
        @StateObject var vm = ViewModel()
        var body: some View {
            Text("Hello, world!")
                .sheet(item: $vm.selectedPlace,
                       onDismiss: didDismiss) {newLocation in
                    //Here Initialise the DetailViewModel with a location
                    DetailsView(detailsVM: DetailsViewModel(location: newLocation))
                }
    
        }
        
        func didDismiss(){
            
        }
    }
    

    DetailsView:

    struct DetailsView: View {
        @StateObject var detailsVM : DetailsViewModel
        var body: some View {
            Text("This is the DetailesView")
        }
    }
    

    DetailsViewModel:

    class DetailsViewModel:ObservableObject{
        @Published var location:Location
        init(location:Location){
            self.location = location
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search