1

I have Student class that is connected to a struct - Details. Which has a nested struct - Subjects. I know how to append to a normal struct or class but I am having difficulties trying to append to a nested struct. What I have is a Form where a student's name and number of subjects are asked after which they have to enter the subject name and grade. Then, press the save button in the Toolbar items/ NavigationBarItems.

class Students: ObservableObject {
    @Published var details = [Details]()
}

struct Details: Identifiable {
    let id = UUID()
    let name: String
    
    struct Subjects: Identifiable {
        let id = UUID()
        let name: String
        let grade: String
    }
    
    let subjects: [Subjects]
}

The View class:

import SwiftUI

struct TestStudentView: View {
    @StateObject var students = Students()
    @State private var name = ""
    @State private var numberOfSubjects = ""
    @State private var subject = [String](repeating: "", count: 10)
    @State private var grade = [String](repeating: "", count: 10)
    @State private var details = [Details.Subjects]()
    var body: some View {
        NavigationView {
            Group {
                Form {
                    Section(header: Text("Student details")) {
                        TextField("Name", text: $name)
                        TextField("Number of subjects", text: $numberOfSubjects)
                    }
                    
                    let count = Int(numberOfSubjects) ?? 0
                    Text("Count: \(count)")
                    Section(header: Text("Subject grades")) {
                        if count>0 && count<10 {
                            ForEach(0 ..< count) { number in
                                TextField("Subject", text: $subject[number])
                                TextField("Grade", text: $grade[number])
                            }
                        }
                    }
                }
                VStack {
                    ForEach(students.details) { student in
                        Text(student.name)
                        ForEach(student.subjects) { subject in
                            HStack {
                                Text("Subject: \(subject.name)")
                                Text("Grade: \(subject.grade)")
                            }
                        }
                    }
                }
            }
            .navigationTitle("Student grades")
            .navigationBarItems(trailing:
                    Button(action: {
                        //let details = Details(name: name, subjects: [Details.Subjects(name: "Physics", grade: "A"), Details.Subjects(name: "Computer Science", grade: "A*")])
                        //students.details.append(details)
                        //^Above to append
                    }, label: {
                    Text("Save")
                })
            )
        }
    }
}

I have tried creating a variable of type [Subjects] but that would not let me append to it after the Textfield values are entered it gives me the error : “Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols” (Which makes sense, as it would require a button). I have also tried appending to it once the save button is pressed using a ForEach but that also gives me the same error.

2 Answers 2

1

I think you want the model change to be the responsibility of your Students class. Try adding a public method to Students and call it from your view, like this:

class Students: ObservableObject {
    @Published var details = [Details]()
  
  public func addDetails(_ details : Details) {
    details.append(details)
  }
}

Then in your button action in the View, replace students.details.append(details) with a call to this method:

let details = Details(name: name, subjects: [Details.Subjects(name: "Physics", grade: "A"), Details.Subjects(name: "Computer Science", grade: "A*")])
students.details.append(details)

Is that what you're trying to do?

Sign up to request clarification or add additional context in comments.

Comments

0

Your view tries to do too much for a single view. Your trying to add students and grades in a single view. The answer of Asperi is correct. However your error is indicating that something else is wrong with your code. Try to run the code below in an isolated environment and it should work fine. For now I just added a save button for each grade it will add the grade to the first student always.

import SwiftUI

class Students: ObservableObject {
    @Published var details = [Details]()
}

struct Details: Identifiable {
    let id = UUID()
    let name: String
    
    struct Subjects: Identifiable {
        let id = UUID()
        let name: String
        let grade: String
    }
    
    var subjects: [Subjects]
}

struct TestStudentView: View {
    @StateObject var students = Students()
    @State private var name = ""
    @State private var numberOfSubjects = ""
    @State private var subject = [String](repeating: "", count: 10)
    @State private var grade = [String](repeating: "", count: 10)
    @State private var details = [Details.Subjects]()
    var body: some View {
        NavigationView {
            Group {
                Form {
                    Section(header: Text("Student details")) {
                        TextField("Name", text: $name)
                        TextField("Number of subjects", text: $numberOfSubjects)
                    }
                    
                    let count = Int(numberOfSubjects) ?? 0
                    Text("Count: \(count)")
                    Section(header: Text("Subject grades")) {
                        if count>0 && count<10 {
                            ForEach(0 ..< count) { number in
                                TextField("Subject", text: $subject[number])
                                TextField("Grade", text: $grade[number])
                                Button(action: {
                                    if students.details.count > 0 {
                                        var test = students.details[0]
                                        students.details[0].subjects.append(Details.Subjects(name: subject[number], grade: grade[number]))
                                    }
                                }) {
                                    Text("Save")
                                }
                            }
                        }
                    }
                }
                VStack {
                    ForEach(students.details) { student in
                        Text(student.name)
                        ForEach(student.subjects) { subject in
                            HStack {
                                Text("Subject: \(subject.name)")
                                Text("Grade: \(subject.grade)")
                            }
                        }
                    }
                }
            }
            .navigationTitle("Student grades")
            .navigationBarItems(trailing:
                    Button(action: {
                        let details = Details(name: name, subjects: [Details.Subjects(name: "Physics", grade: "A"), Details.Subjects(name: "Computer Science", grade: "A*")])
                        students.details.append(details)
                        //^Above to append
                    }, label: {
                    Text("Save")
                })
            )
        }
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.