Sort SwiftData Query shown in SwiftUI Table on macOS

565 views Asked by At

I have a mini macOS app, presenting some data persisted in SwiftData on a Table.

I want to sort the data by clicking on Table's column headers so I have a KeyPathComparator on the table.

But since the result of @Query is get-only, I am not able to set sort of items. Any suggestions?

Here is my table:

    @Query private var items: [Product]
    
    @State private var sortOrder = [KeyPathComparator(\Product.category?.name)]
    
    var body: some View {
        Table(items, sortOrder: $sortOrder) {
            TableColumn("Category",value: \.category!.name)
            TableColumn("Design", value: \.design!.name)
            TableColumn("Market", value: \.market!.name)
            TableColumn("Link", value: \.link)
        }
        .onChange(of: sortOrder, { oldValue, newValue in
            items.sort(using: newValue) // ERROR: Cannot use mutating member on immutable value: 'items' is a get-only property
        })

    }
1

There are 1 answers

6
Joakim Danielson On

As mentioned in the comments, @Query and Table doesn't work that well together at least not when it comes to sorting. Here is one way to work around it by having a second array as a computed properties that gets sorted on the Table's sort order parameter.

@Query private var items: [Product]

@State private var sortOrder = [KeyPathComparator(\Product.category?.name)]

var sortedItems: [Product] {
    items.sorted(using: sortOrder)
}

var body: some View {
    Table(sortedItems, sortOrder: $sortOrder) {
        TableColumn("Category",value: \.category!.name)
        TableColumn("Design", value: \.design!.name)
        TableColumn("Market", value: \.market!.name)
        TableColumn("Link", value: \.link)
    }
}

If one uses the Table(...) { ... } rows: {...} form then you can skip the computed property and sort in the ForEach directly

Table(of: Product.self, sortOrder: $sortOrder) {
    TableColumn...
}  rows: {
    ForEach(items.sorted(using: sortOrder)) {
        TableRow($0)
    }
}

I am not sure if it makes any difference performance/memory wise but it's a bit less code.