How SwiftUI Table sorting work with multiple KeyPathComparator ?

Hello everyone,

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] = []


struct StudentGradeHistoryView: View {
    @State var students = _students

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

    var body: some View {
        NavigationStack {
            Table(of: Student.self,
                  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 in this way

Answered by DTS Engineer in 809559022

@cp-divyesh-v I would recommend you review Building a Great Mac App with SwiftUI sample code. It has an example that walks you through how you should create sortable columns.

However here's an example :

struct Plant: Identifiable {
    var id = UUID()
    var variety: String
    var daysToMaturity: Int
    var datePlanted: Date
    var harvestDate: Date
    var lastWateredOn: Date
    var favorite: Bool
}

@Observable
class DetailManager {
    var sortOrder: [KeyPathComparator<Plant>] = [
       .init(\.daysToMaturity, order: SortOrder.reverse),
       .init(\.variety, order: SortOrder.reverse)

   ]
    
    var data =  [
        Plant(variety: "Tomato", daysToMaturity: 80, datePlanted: Date(), harvestDate: Date(), lastWateredOn: Date(), favorite: true),
        Plant(variety: "Carrot", daysToMaturity: 60, datePlanted: Date(), harvestDate: Date(), lastWateredOn: Date(), favorite: false),
        Plant(variety: "Pepper", daysToMaturity: 90, datePlanted: Date(), harvestDate: Date(), lastWateredOn: Date(), favorite: false)
    ]

    var plants: [Plant] {
        data.sorted(using: sortOrder)
    }
}

struct GardenDetailTable: View {
    @State private var selection = Set<Plant.ID>()
    @State private var viewModel = DetailManager()
    
    var body: some View {
        Table(selection: $selection, sortOrder: $viewModel.sortOrder) {
            TableColumn("Variety", value: \.variety)

            TableColumn("Days to Maturity", value: \.daysToMaturity) { plant in
                Text(plant.daysToMaturity.formatted())
            }

            TableColumn("Date Planted", value: \.datePlanted) { plant in
                Text(plant.datePlanted.formatted(date: .abbreviated, time: .omitted))
            }

            TableColumn("Harvest Date", value: \.harvestDate) { plant in
                Text(plant.harvestDate.formatted(date: .abbreviated, time: .omitted))
            }

            TableColumn("Last Watered", value: \.lastWateredOn) { plant in
                Text(plant.lastWateredOn.formatted(date: .abbreviated, time: .omitted))
            }
        } rows: {
            ForEach(viewModel.plants) { plant in
                TableRow(plant)
            }
        }
    }
}

@cp-divyesh-v I would recommend you review Building a Great Mac App with SwiftUI sample code. It has an example that walks you through how you should create sortable columns.

However here's an example :

struct Plant: Identifiable {
    var id = UUID()
    var variety: String
    var daysToMaturity: Int
    var datePlanted: Date
    var harvestDate: Date
    var lastWateredOn: Date
    var favorite: Bool
}

@Observable
class DetailManager {
    var sortOrder: [KeyPathComparator<Plant>] = [
       .init(\.daysToMaturity, order: SortOrder.reverse),
       .init(\.variety, order: SortOrder.reverse)

   ]
    
    var data =  [
        Plant(variety: "Tomato", daysToMaturity: 80, datePlanted: Date(), harvestDate: Date(), lastWateredOn: Date(), favorite: true),
        Plant(variety: "Carrot", daysToMaturity: 60, datePlanted: Date(), harvestDate: Date(), lastWateredOn: Date(), favorite: false),
        Plant(variety: "Pepper", daysToMaturity: 90, datePlanted: Date(), harvestDate: Date(), lastWateredOn: Date(), favorite: false)
    ]

    var plants: [Plant] {
        data.sorted(using: sortOrder)
    }
}

struct GardenDetailTable: View {
    @State private var selection = Set<Plant.ID>()
    @State private var viewModel = DetailManager()
    
    var body: some View {
        Table(selection: $selection, sortOrder: $viewModel.sortOrder) {
            TableColumn("Variety", value: \.variety)

            TableColumn("Days to Maturity", value: \.daysToMaturity) { plant in
                Text(plant.daysToMaturity.formatted())
            }

            TableColumn("Date Planted", value: \.datePlanted) { plant in
                Text(plant.datePlanted.formatted(date: .abbreviated, time: .omitted))
            }

            TableColumn("Harvest Date", value: \.harvestDate) { plant in
                Text(plant.harvestDate.formatted(date: .abbreviated, time: .omitted))
            }

            TableColumn("Last Watered", value: \.lastWateredOn) { plant in
                Text(plant.lastWateredOn.formatted(date: .abbreviated, time: .omitted))
            }
        } rows: {
            ForEach(viewModel.plants) { plant in
                TableRow(plant)
            }
        }
    }
}
How SwiftUI Table sorting work with multiple KeyPathComparator ?
 
 
Q