How SwiftUI Table sorting works ?

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. Need help with the answers for the questions posted below the code.

import SwiftUI

struct Person: Identifiable {
    let givenName: String
    let familyName: String
    let emailAddress: String
    let id = UUID()
}

private var people = [
    Person(givenName: "f1", familyName: "l1", emailAddress: "e1@example.com"),
    Person(givenName: "f2", familyName: "l2", emailAddress: "e2@example.com"),
    Person(givenName: "f3", familyName: "l3", emailAddress: "e3@example.com")
]

struct ContentView: View {
    @State private var selected = Set<Person.ID>() 

    // Question 1
    @State private var sortOrder = [KeyPathComparator(\Person.givenName)]

    var body: some View {
        Table(people, selection: $selected, sortOrder: $sortOrder) {

            TableColumn("Given name", value: \.givenName)

            TableColumn("Family name", value: \.familyName)

            TableColumn("Email", value: \.emailAddress)
        }
        // Question 2
        .onChange(of: sortOrder) { newOrder in
            people.sort(using: newOrder)
        }
    }
}

Question 1 : I want to be able to sort by all the columns. How many KeyPathComparators should be in the sortOrder array?

Question 2: What is the value of newOrder? Is it same as the sortOrder? What does it contain?

Question 3: sortOrder contains the comparator for only givenName. How does the above code able to provide sort functionality for all the columns?

Answered by Frameworks Engineer in 743556022

Hi himav, for the follow up questions you had, clicking on the "Family name" column header would change the sortOrder array property to include the Comparator for that column (or reverse that Comparator if the sortOrder array already had one for that column). You could see the sortOrder binding as a representation of how different columns want to take part in sorting - and then you could sort the data accordingly.
For custom Comparator on individual columns, you could use one of TableColumn's initializers that come with comparator as a parameter, and that custom comparator you provided will be included into sortOrder if you click on the corresponding column header. Hope this helps!

Question 1 : I want to be able to sort by all the columns. How many KeyPathComparators should be in the sortOrder array?

Surprisingly, a single comparator works for all columns

.

Question 2: What is the value of newOrder? Is it same as the sortOrder? What does it contain?

You get the same behaviour with this:

        .onChange(of: sortOrder) { _ in
            people.sort(using: sortOrder)
        }

.

Question 3: sortOrder contains the comparator for only givenName. How does the above code able to provide sort functionality for all the columns?

Same as question 1 ?

I found this tutorial interesting. https://www.kodeco.com/books/macos-by-tutorials/v1.0/chapters/4-using-tables-custom-views

And changed your code to make it work better (immediately change rows when tapping another column header)

struct Person: Identifiable {
    let givenName: String
    let familyName: String
    let emailAddress: String
    let id = UUID()
}

private var people = [
    Person(givenName: "f1", familyName: "Bob", emailAddress: "xbob@ example.com"),
    Person(givenName: "f2", familyName: "Alice", emailAddress: "calice@ example.com"),
    Person(givenName: "f3", familyName: "Don", emailAddress: "bDon@ example.com"),
    Person(givenName: "f4", familyName: "Chuck", emailAddress: "mChuck@ example.com")
]

struct ContentView: View {
    @State private var selected = Set<Person.ID>()

    // Question 1
    @State private var sortOrder = [KeyPathComparator(\Person.givenName)]

    var sortedTableData: [Person] {
      return people.sorted(using: sortOrder)
    }

    var body: some View {
        Table(sortedTableData, selection: $selected, sortOrder: $sortOrder) {

            TableColumn("Given name", value: \.givenName)

            TableColumn("Family name", value: \.familyName)

            TableColumn("Email", value: \.emailAddress)
        }
        // Question 2
//        .onChange(of: sortOrder) { _ in
//            people.sort(using: sortOrder)
//        }
    }
}

Still not completely clear to me. Lets say I click on "Family name" column, what happens next? Does the value of sortOrder variable change if I click on "Family name" column? If yes, what will be the new value? Also, how can I use custom Comparator for different columns?

Accepted Answer

Hi himav, for the follow up questions you had, clicking on the "Family name" column header would change the sortOrder array property to include the Comparator for that column (or reverse that Comparator if the sortOrder array already had one for that column). You could see the sortOrder binding as a representation of how different columns want to take part in sorting - and then you could sort the data accordingly.
For custom Comparator on individual columns, you could use one of TableColumn's initializers that come with comparator as a parameter, and that custom comparator you provided will be included into sortOrder if you click on the corresponding column header. Hope this helps!

How SwiftUI Table sorting works ?
 
 
Q