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