arraysswiftswiftuiedit

Best Way to Update/Edit an Array Element in SwiftUI


I have an array of identifiable Training elements. Each Training element only has two properties, name and isRequired.

What is the most straight-forward (Swiftyiest) way to update the existing values of an array element to new edited values... the edited values will then later be submitted to the database.

Is it possible to set the state in the EditTraining View (Child) to the passed in (Parent) values and then edit the state in the child view?

I've been stuck on this longer than I'll ever admit.

Thank you very much for any help at all!

Here is the code:

import SwiftUI

struct Training: Identifiable {
    let id: String
    let trainingName: String
    let isRequired: Bool
}

class GetTrainings: ObservableObject {
    @Published var items = [Training]()

    init() {
        self.items = [
            Training(id: "ttt1", trainingName: "Safety", isRequired: true),
            Training(id: "ttt2", trainingName: "Administrative", isRequired: false),
            Training(id: "ttt3", trainingName: "Computer", isRequired: true),
            Training(id: "ttt4", trainingName: "People", isRequired: true),
            Training(id: "ttt5", trainingName: "Managerial", isRequired: true),
        ]
    }
}

struct TrainingList: View {

    @ObservedObject var trainings = GetTrainings()

    var body: some View {
        NavigationView {
            VStack {
                List {

                    ForEach(trainings.items) { training in

                        HStack {
                            NavigationLink(destination: TrainingView(training: training)) {
                                Text("\(training.trainingName)")
                            }
                        }
                    }

                }
            }.navigationBarTitle("Training List")
        }
    }
}

struct TrainingView: View {

    var training: Training

    var body: some View {

        VStack {

            Text("\(training.trainingName)").font(.body)
            Text("\(training.isRequired == true ? "Required Training" : "Training Not Required")")

            HStack {
                NavigationLink(destination: EditTraining(training: training)) {
                    Text("Edit Training Details")
                }
            }
        }.navigationBarTitle("\(training.trainingName) Page", displayMode: .inline)

    }
}

struct EditTraining: View {

    var training: Training

    // Can I set the state values to the passed in values ???
    @State private var newName: String = ""
    @State private var isRequiredTraining: Bool = false
    //@Binding var name: String = training.trainingName ????

    private func submitData() {

        let newName = self.newName
        let newBoolVal = self.isRequiredTraining

        print("Firebase Sync Id is :\(training.id) Text: \(newName) and Bool: \(newBoolVal)")

    }

    var body: some View {
        VStack {
            Form {
                Section (header: Text("Edit"))  {

                    Text("\(training.trainingName)")
                    /* TextField should Populate With passed In Training Name Here*/
                    TextField("New Name", text: self.$newName)
                    Toggle(isOn: self.$isRequiredTraining) {
                        Text("Is Required")
                    }
                }

                Section {

                    Button(action: {
                        self.submitData()
                    }) {
                        Text("Submit")
                    }

                }
            }
        }.navigationBarTitle("Edit Training Page", displayMode: .inline)
    }
}

struct ContentView: View {

    var body: some View {

        VStack {

            TrainingList()

        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Solution

  • This is the case when as I see it is more preferable to use ObservableObject for models, because it allows to pass by reference model object deep into hierarchy and keep it up to date during workflow.

    Here is a solution. Tested with Xcode 11.4 / iOS 13.4

    demo

    class Training: ObservableObject, Identifiable {
        let id: String
        @Published var trainingName: String
        @Published var isRequired: Bool
    
        init(id: String, trainingName: String, isRequired: Bool) {
            self.id = id
            self.trainingName = trainingName
            self.isRequired = isRequired
        }
    }
    
    class GetTrainings: ObservableObject {
        @Published var items = [Training]()
    
        init() {
            self.items = [
                Training(id: "ttt1", trainingName: "Safety", isRequired: true),
                Training(id: "ttt2", trainingName: "Administrative", isRequired: false),
                Training(id: "ttt3", trainingName: "Computer", isRequired: true),
                Training(id: "ttt4", trainingName: "People", isRequired: true),
                Training(id: "ttt5", trainingName: "Managerial", isRequired: true),
            ]
        }
    }
    
    struct TrainingList: View {
    
        @ObservedObject var trainings = GetTrainings()
    
        var body: some View {
            NavigationView {
                VStack {
                    List {
    
                        ForEach(trainings.items) { training in
    
                            HStack {
                                NavigationLink(destination: TrainingView(training: training)) {
                                    Text("\(training.trainingName)")
                                }
                            }
                        }
    
                    }
                }.navigationBarTitle("Training List")
                .onAppear {
                    self.trainings.objectWillChange.send() // refresh
                }
            }
        }
    }
    
    struct TrainingView: View {
    
        @ObservedObject var training: Training
    
        var body: some View {
    
            VStack {
    
                Text("\(training.trainingName)").font(.body)
                Text("\(training.isRequired == true ? "Required Training" : "Training Not Required")")
    
                HStack {
                    NavigationLink(destination: EditTraining(training: training)) {
                        Text("Edit Training Details")
                    }
                }
            }.navigationBarTitle("\(training.trainingName) Page", displayMode: .inline)
    
        }
    }
    
    struct EditTraining: View {
    
        @ObservedObject var training: Training
    
        @State private var newName: String
        @State private var isRequiredTraining: Bool
    
        init(training: Training) {
            self.training = training
            self._newName = State(initialValue: training.trainingName)
            self._isRequiredTraining = State(initialValue: training.isRequired)
        }
    
        private func submitData() {
    
            let newName = self.newName
            let newBoolVal = self.isRequiredTraining
    
            print("Firebase Sync Id is :\(training.id) Text: \(newName) and Bool: \(newBoolVal)")
    
            self.training.trainingName = newName
            self.training.isRequired = newBoolVal
        }
    
        var body: some View {
            VStack {
                Form {
                    Section (header: Text("Edit"))  {
    
                        Text("\(training.trainingName)")
                        /* TextField should Populate With passed In Training Name Here*/
                        TextField("New Name", text: self.$newName)
                        Toggle(isOn: self.$isRequiredTraining) {
                            Text("Is Required")
                        }
                    }
    
                    Section {
    
                        Button(action: {
                            UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),
                                to:nil, from:nil, for:nil)
                            self.submitData()
                        }) {
                            Text("Submit")
                        }
    
                    }
                }
            }.navigationBarTitle("Edit Training Page", displayMode: .inline)
        }
    }