Search code examples
foreachswiftuiidentifiable

ForEach not working with Identifiable & id = UUID()


import SwiftUI

struct TestStudentView: View {
    @StateObject var students = Students()
    @State private var name = ""
    @State private var numberOfSubjects = ""
    @State private var subjects = [Subjects](repeating: Subjects(name: "", grade: ""), count: 10)
    
    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, id: \.self) { number in
                                TextField("Subjects", text: $subjects[number].name)
                                TextField("Grade", text: $subjects[number].grade)
                            }
                        }
                    }
                }
                VStack {
                    ForEach(students.details) { student in
                        Text(student.name)
                        ForEach(student.subjects) { subject in //Does not work as expected
                        //ForEach(student.subjects, id:\.id) { subject in //Does not work as expected
                        //ForEach(student.subjects, id:\.self) { subject in //works fine with this
                            HStack {
                                Text("Subject: \(subject.name)")
                                Text("Grade: \(subject.grade)")
                            }
                        }
                    }
                }
            }
            .navigationTitle("Student grades")
            .navigationBarItems(trailing:
                    Button(action: {
                        let details = Details(name: name, subjects: subjects)
                        students.details.append(details)
                        
                    }, label: {
                    Text("Save")
                })
            )
        }
    }
}

struct TestStudentView_Previews: PreviewProvider {
    static var previews: some View {
        TestStudentView()
    }
}

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

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

struct Subjects: Identifiable, Hashable {
    let id = UUID()
    var name: String
    var grade: String
}

When I use - "ForEach(student.subjects, id:.id) { subject in" under normal circumstances it is supposed to work as id = UUID and the incorrect output is as follows:

enter image description here

then as the class conforms to Identifiable I tried - "ForEach(student.subjects) { subject in" it still does not work correctly. However, when I do - "ForEach(student.subjects, id:.self) { subject in" except I had to have the class conform to hashable and gives me the correct expected output. The correct output which is shown:

enter image description here


Solution

  • You need to use a map instead of repeating.

    By using Array.init(repeating:) will invoke the Subjects to initialize only one time, and then insert that object into the array multiple times.

    So all, in this case, all id is same.

    You can check by just print all id in by this .onAppear() { print(subjects.map({ (sub) in print(sub.id) }))

    struct TestStudentView: View {
        @StateObject var students = Students()
        @State private var name = ""
        @State private var numberOfSubjects = ""
        @State private var subjects: [Subjects] = (0...10).map { _ in
            Subjects(name: "", grade: "")
        } //<-- Here