How to sort my Core Data entities using the new query properties from AppIntents

TL;DR

How to create an NSSortDescriptor using a PartialKeyPath instead of a KeyPath?

Or

How to convert a PartialKeyPath to a KeyPath?

Details

I'm using the new query properties from AppIntents to filter my app's data in Shortcuts, but I'm unable to map the sorting attribute it gives me to the sorting attribute Core Data expects in the predicate.

Here's my EntityPropertyQuery implementation:

extension ArtistQuery: EntityPropertyQuery {

    static var sortingOptions = SortingOptions {
        SortableBy(\ArtistEntity.$name)
    }

    static var properties = QueryProperties {
        Property(\ArtistEntity.$name) {
            EqualToComparator { NSPredicate(format: "name = %@", $0) }
            ContainsComparator { NSPredicate(format: "name CONTAINS %@", $0) }
        }
    }

    func entities(matching comparators: [NSPredicate],
                  mode: ComparatorMode,
                  sortedBy: [Sort<ArtistEntity>],
                  limit: Int?) async throws -> [ArtistEntity] {
        Database.shared.findArtists(matching: comparators,
                                    matchAll: mode == .and,
                                    sorts: sortedBy.map { NSSortDescriptor(keyPath: $0.by, ascending: $0.order == .ascending) })
    }

}

And my findArtists method is implemented as follows:

static func findArtists(matching comparators: [NSPredicate],
                        matchAll: Bool,
                        sorts: [NSSortDescriptor]) -> [EArtist] {
    ...
}

As we can see in the entities(matching:) function, I'm using the by attribute from the sortedBy parameter to create the NSSortDescriptor, but it doesn't work because the NSSortDescriptor init expects a KeyPath, not a PartialKeyPath:

Cannot convert value of type 'PartialKeyPath<ArtistEntity>' to expected argument type 'KeyPath<Root, Value>'

So, can I create an NSSortDescriptor using a PartialKeyPath instead of a KeyPath? Or maybe converting a PartialKeyPath to a KeyPath?

Answered by marcosatanaka in 725722022

I found a solution thanks to this Gist. There's no way to directly convert from an EntityQuerySort to an NSSortDescriptor. Instead, we have to convert it manually:

private func toSortDescriptor(_ sortedBy: [Sort<ArtistEntity>]) -> [NSSortDescriptor] {
    var sortDescriptors = [NSSortDescriptor]()
    if let sort = sortedBy.first {
        switch sort.by {
        case \.$name:
            sortDescriptors.append(NSSortDescriptor(keyPath: \EArtist.name, ascending: sort.order == .ascending))
        default:
            break
        }
    }
    return sortDescriptors
}
Accepted Answer

I found a solution thanks to this Gist. There's no way to directly convert from an EntityQuerySort to an NSSortDescriptor. Instead, we have to convert it manually:

private func toSortDescriptor(_ sortedBy: [Sort<ArtistEntity>]) -> [NSSortDescriptor] {
    var sortDescriptors = [NSSortDescriptor]()
    if let sort = sortedBy.first {
        switch sort.by {
        case \.$name:
            sortDescriptors.append(NSSortDescriptor(keyPath: \EArtist.name, ascending: sort.order == .ascending))
        default:
            break
        }
    }
    return sortDescriptors
}
How to sort my Core Data entities using the new query properties from AppIntents
 
 
Q