Search code examples
swiftswiftuiproperty-wrapper

SwiftUI Passing variables in view hierarchy


I have a question regarding the architecture of my Swift / SwiftUI app consisting of a ListView, detail views and the detail views hold 2 input views. Specifically I want to know if this is the right way to do it.

The architecture is as follows:

The list view initiates a @StateObjct of the view model controller.

@StateObject var viewModel = ListViewModelController()

View model controller:

class ListViewModelController: ObservableObject {
@Published var model: ListViewModel

init() {
    self.model = ListViewModel()
 }
}

The List view model looks as follows:

struct Exercise: Hashable, Identifiable {
let id =  UUID()
var name: String
var weight: String
var repetitions: String
}

struct ListViewModel {
var exercises = [
    Exercise(name: "crunches", weight: "80 kg", repetitions: "100"),
    Exercise(name: "curls", weight: "10 kg", repetitions: "12"),
    Exercise(name: "dips", weight: "Bodyweight", repetitions: "10")
]
}

I pass the exercise variable to the detail view in the following way:

List {
        ForEach($viewModel.model.exercises, id: \.self) {$exercise in
            ExerciseConfigutationView(exercise: $exercise)
        }
        .onDelete(perform: delete)
        .onMove(perform: move)
    }

The detail view accepts the exercise variable as a binding:

@Binding var exercise: Exercise

The problem is, every time I try to present the ListView, the programm freezes without a warning or error. I assume I have misunderstood something about the binding / property wrappers.

Thanks for your help.


Solution

  • ForEach with id: \.self is the mistake however a more serious problem is that in SwiftUI we do not use view model objects, only model objects. The View data structs are already the view model that SwiftUI uses to create and update actual views like UILabels, etc. on the screen.

    Here is your fixed code:

    @StateObject var model = ListModel()
    
    //The List model:
    class ListModel: ObservableObject {
        @Published var var exercises = [Exercise(name: "crunches", weight: "80 kg", repetitions: "100"),
                                        Exercise(name: "curls", weight: "10 kg", repetitions: "12"),
                                        Exercise(name: "dips", weight: "Bodyweight", repetitions: "10")]
    }
    
    struct Exercise: Hashable, Identifiable {
        let id =  UUID()
        var name: String
        var weight: String
        var repetitions: String
    }
    
    // Pass the exercise variable to the detail view in the following way:
    List {
            ForEach($model.exercises) { $exercise in
                ExerciseConfigurationView(exercise: $exercise)
            }
            .onDelete(perform: delete)
            .onMove(perform: move)
        }
    
    
    // pass the exercise variable to the detail view in the following way:
    @Binding var exercise: Exercise