swiftsortingswiftuiswift-keypathswiftui-table

How SwiftUI Table sorting work with multiple KeyPathComparator?


I am new to the Swift and the SwiftUI. Trying to understand how sorting works on SwiftUI Table view.

The following code does what I intend to do, but I am not able to understand how does it actually work with multiple key path. Need help with the answers for the questions posted below the code.

// MARK: - Student Model
struct Student: Codable, Identifiable {
    let id: String
    let name: String
    let gradeHistory: GradeHistory

    enum CodingKeys: String, CodingKey {
        case id, name
        case gradeHistory = "grade_history"
    }
}

// MARK: - GradeHistory Model
struct GradeHistory: Codable, Identifiable{
    let id: String?
    let semester: String
    let subjects: Subjects

    init(
        id: String? = UUID().uuidString,
        semester: String,
        subjects: Subjects
    ) {
        self.id = id ?? UUID().uuidString
        self.semester = semester
        self.subjects = subjects
    }
}

// MARK: - Subjects Model
struct Subjects: Codable, Identifiable {
    let id: String?
    let math: Int
    let science: Int
    let english: Int
    let physics: Int
    let computer: Int
    let socialScience: Int

    init(
        id: String? = nil,
        math: Int,
        science: Int,
        english: Int,
        physics: Int,
        computer: Int,
        socialScience: Int
    ) {
        self.id = id ?? UUID().uuidString
        self.math = math
        self.science = science
        self.english = english
        self.physics = physics
        self.computer = computer
        self.socialScience = socialScience
    }

    enum CodingKeys: String, CodingKey {
        case id = "id"
        case math = "Math"
        case science = "Science"
        case english = "English"
        case physics = "Physics"
        case computer = "Computer"
        case socialScience = "Social Science"
    }
}

let _students: [Student] = [
    Student(
        id: "S001",
        name: "John Doe",
        gradeHistory: GradeHistory(
            semester: "Fall 2022",
            subjects: Subjects(
                math: 85,
                science: 78,
                english: 90,
                physics: 88,
                computer: 95,
                socialScience: 80
            )
        )
    ),
    Student(
        id: "S002",
        name: "Jane Smith",
        gradeHistory: GradeHistory(
            semester: "Spring 2023",
            subjects: Subjects(
                math: 92,
                science: 85,
                english: 89,
                physics: 91,
                computer: 94,
                socialScience: 88
            )
        )
    ),
    Student(
        id: "S003",
        name: "Emily Johnson",
        gradeHistory: GradeHistory(
            semester: "Fall 2023",
            subjects: Subjects(
                math: 78,
                science: 81,
                english: 82,
                physics: 86,
                computer: 90,
                socialScience: 84
            )
        )
    )
]


struct StudentGradeHistoryView: View {
    @State var students = _students

    @State private var sortOrder = [KeyPathComparator(\Student.name)]

    var body: some View {
        NavigationStack {
            Table(students,
                  selection: students.selectedStudents,
                  sortOrder: $sortOrder) {
                TableColumn("Index") { student in
                    let index = (students.firstIndex(
                        where: { $0.id == student
                            .id }) ?? 0)
                    Text("No. \(index + 1)")
                }

                TableColumn("Id", value: \.id)

                TableColumn("Name", value: \.name)
                    .width(min: 150)

                TableColumn("Math") { student in
                    Text("\(student.gradeHistory.subjects.math)")
                        .foregroundStyle(
                            gradeColor(for: student.gradeHistory.subjects.math)
                        )
                }
                .defaultVisibility(.automatic)
                TableColumn("Science") { student in
                    Text("\(student.gradeHistory.subjects.science)")
                        .foregroundStyle(gradeColor(for: student.gradeHistory.subjects.science))
                }
                TableColumn("English") { student in
                    Text("\(student.gradeHistory.subjects.english)")
                        .foregroundStyle(gradeColor(for: student.gradeHistory.subjects.english))
                }
                TableColumn("Physics") { student in
                    Text("\(student.gradeHistory.subjects.physics)")
                        .foregroundStyle(gradeColor(for: student.gradeHistory.subjects.physics))
                }
                TableColumn("Computer") { student in
                    Text("\(student.gradeHistory.subjects.computer)")
                        .foregroundStyle(gradeColor(for: student.gradeHistory.subjects.computer))
                }
                TableColumn("Social Science") { student in
                    Text("\(student.gradeHistory.subjects.socialScience)")
                        .foregroundStyle(gradeColor(for: student.gradeHistory.subjects.socialScience))
                }
            }
            .onChange(of: sortOrder) {
                students.sort(using: sortOrder)
            }
        }
    }
}

My question is how I can use KeyPathComparator in this model to sort data with multiple comparator paths like

    @State private var sortOrder = [
        KeyPathComparator(\Student.name),
        KeyPathComparator(\Subjects.math)
    ]

It's not working if i user this sortOrder.

I was expectin that each table column should be sortable but i don't know why different KeyPathCoparator is not working.


Solution

  • The problem is that you are not using an init for TableColumn that supports sorting, see the documentation for all alternatives but here is one example of making the math column sortable

    TableColumn("Math Sortable", value: \.gradeHistory.subjects.math) {
        Text("\($0.gradeHistory.subjects.math)")
    }
    

    and with that you can now get correct sorting from the start

    @State private var sortOrder = [
        KeyPathComparator(\Student.name),
        KeyPathComparator(\Student.gradeHistory.subjects.math)
    ]