4

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
        })

    }
4
  • Nope, it gives the error mentioned in the question. So, I wrapped the table in it's own View and passed the items into it. Now sorting works but change in objects (after CRUD) does not trigger the table to refresh it's contents. But maybe I can create a calculated property that takes items and sort by given order. Hmm, I will give it a shot Commented Oct 20, 2023 at 14:05
  • And that does not work - as I expected. Computed properties are not allowed to be wrapped by property wrappers. Commented Oct 20, 2023 at 14:17
  • Sort on query is unimportant. Actually I need to remove it from my code. An unnecessary cpu work. Question is updated. Commented Oct 20, 2023 at 14:41
  • Ok, I think I understand better now. There is no good solution for this as of now where @Query and Table work smoothly together but I will provide a workaround. Commented Oct 20, 2023 at 15:19

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

7 Comments

the issue with this solution is that the performance is miserable with a higher amount of records.
Different requirements needs different solutions
How do you know his requirements, where it is said up to how many records it have to work ?
I don't and this answer wasn't accepted either so maybe that is an issue but if you have a large amount of records then maybe using @Query is not the right solution to start with and then we have a very different question. I said this is a work around so take it for that. If you have a similar problem with a large amount of data then post a new question about it or if you have a better solution then post another answer.
@Qurey works well with a lot of data but only to the point if we add your suggested filtering. Filtering, to make it as performant as possible should be applied on database level. So why not just convert a KeyPathComparator to a SortDescriptor and apply t to the query :-)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.