Pass data to an @Observable model

Overview

  • I have a navigation split view.
  • The detail view contains a model now this model depends on id from the parent view.

Questions

  1. How can I pass data from the parent view and yet create the view in the detail view?
  2. Or should I be pass the model from the parent view, but the problem is the parent view needs to persist model.
  3. Or is there a better approach?
Answered by ChrisDE in 896636022

The 3rd option looks good.

.onAppear() and .task{} are better suited for processing data when the view is displayed than the init{} function. However, a query could be created in init{} to load data. There are different ways to do this; it always depends on what makes sense for the situation.

Hi,

in the parent view the data is like this

@State private var value: Int = 1

and in the child view

@Binding var value: Int

In this case, the parent view controls the data, and the child view can use and modify it.

Is that what you mean?

Christian

@ChrisDE Consider the following model

@Observable
class Model {
    let id: Int
    
    init(id: Int) {
        self.id = id
    }
}
  1. Should the model be created in the parent view (as @State property in parent view) and passed to the detail view?

  2. Or should I create model in the detail view (as @State property in the detail view)? But this id needs to come from the parent view.

@ChrisDE

Questions

  1. Should CarDetailViewModel be created in CarDetailView?
  2. Or should CarDetailViewModel be created in ContentView and passed to CarDetailView?

Code

ContentView

struct ContentView: View {
    @State private var selectedCarID: UUID?
    
    var body: some View {
        NavigationSplitView {
            CarListView(selectedCarID: $selectedCarID)
        } detail: {
            if let selectedCarID {
                CarDetailView(carID: selectedCarID)
            }
        }
    }
}

CarListViewModel

@Observable
class CarListViewModel {
    let cars = [
        Car(id: UUID(), name: "aaa"),
        Car(id: UUID(), name: "bbb")
    ]
}

CarListView

import SwiftUI

struct CarListView: View {
    @Binding var selectedCarID: UUID?
    @State var model = CarListViewModel()
    
    var body: some View {
        List(model.cars, selection: $selectedCarID) { car in
            VStack(alignment: .leading) {
                Text(car.name)
                Text("\(car.id)")
            }
        }
    }
}

CarDetailViewModel

@Observable
class CarDetailViewModel {
    let carID: UUID
    
    init(carID: UUID) {
        self.carID = carID
    }
}

CarDetailView

import SwiftUI

struct CarDetailView: View {
    @State private var model: CarDetailViewModel
    
    init(carID: UUID) {
        let model = CarDetailViewModel(carID: carID)
        _model = State(wrappedValue: model)
    }
    
    var body: some View {
        Text("\(model.carID)")
    }
}

Do you need a view model? A view can include a view model, but it doesn't have to. I only use view models when complex validations or extra data are loaded that require additional effort. Otherwise, @Query is sufficient to load the data.

I assume the details aren't relevant in the parent view and therefore haven't been loaded yet.

In the DetailView, you could use

@Query private var details: [CarDetails]
init(carID: UUID) {
  let predicate = #Predicate<CarDetails> { $0.id = carID}
  _details = Query (filter: predicate)
}

I recommend watching Stewart Lynch's videos on YouTube. He explains many topics in an easy-to-understand way.

Thanks @ChrisDE, in my case I need a model

The example shown above is a simplified version of the actual use case.

The problem / confusion is when a model depends on external input there are 3 options that I am aware of:

Option 1:

  • Create the model in the child view
  • In the initializer of child view accept the input and initialize the model in the init of the view
  • Problem with this approach is a view's init could be called multiple times and new instances would be created unnecessarily. These new instances would be discarded but just an unnecessary creation

Option 2:

  • Create the model in the parent view
  • Pass the model to the child view
  • The problem is the child view model is created ahead of time and may be the user never goes to the detail view and still gets created unnecessarily

Option 3

  • Create a model in the child view
  • The model contains the carID as an optional property and is nil.
  • Child view accepts the input
  • In the task of the child view update the model.carID as the actual view and do the fetch there.

Presently I am using Option 3, I wanted to know if there was a better way

Accepted Answer

The 3rd option looks good.

.onAppear() and .task{} are better suited for processing data when the view is displayed than the init{} function. However, a query could be created in init{} to load data. There are different ways to do this; it always depends on what makes sense for the situation.

Pass data to an &#64;Observable model
 
 
Q