How to sort SwiftData queries using key paths or SortDescriptor
How to sort SwiftData queries using key paths or SortDescriptor êŽë š
Updated for Xcode 15
Sorting SwiftData queries is either done with a key path for simple sorts, or an array of SortDescriptor
for more complex sorts. Some query variants â e.g. creating a FetchDescriptor
by hand â only support the array approach, whereas using @Query
supports both.
For the simplest approach using @Query
, you can specify your sort order as a key path on the model youâre querying. So, if we had a Movie
model, we could load all movies sorted alphabetically by their name like this:
@Query(sort: \Movie.name) var movies: [Movie]
And if you wanted a descending alphabetical sort, weâd use this:
@Query(sort: \Movie.name, order: .reverse) var movies: [Movie]
You can even dig into relationship data, for example sorting by the name of a movieâs director:
@Query(sort: \Movie.director.name) var movies: [Movie]
This sort key path can be any directly comparable property of your model, so we could sort by movie name, movie release date, director age, or anything similar.
Important
Although you can make your models conform to Comparable
and use them in these sort orders, it will not work as intended. Behind the scenes SwiftData will insert your model to its own table with a primary key integer, then sort by that integer â youâre effectively sorting by when each object was inserted into your context.
To get more advanced sorting, you can specify an array of SortDescriptor
. This approach works much the same with both @Query
and FetchDescriptor
, which does make life a little easier. The advantage of this approach is that you can specify multiple sort orders to have them applied in order: if the first two fields sort the same for two objects, then they are sorted by the second field, then the third, and so on.
For example, we could sort a list of movies alphabetically, then release date reverse, then finally by director name:
@Query(sort: [SortDescriptor(\name), SortDescriptor(\Movie.releaseDate, order: .reverse), SortDescriptor(\Movie.director.name)]) var movies: [Movie]
There are lots of movies called âThe Awakeningâ (over 30!), and itâs not impossible to have two movies with the same name and same release date, but it would be supremely unlikely to have name, release date, and director name all be the same.
As I said, this same approach works great with a standalone FetchDescriptor
, although here the type inference for key paths works a little better so we can skip the model name:
var descriptor = FetchDescriptor<Movie>(sortBy: [SortDescriptor(\.name)])
let results = (try? modelContext.fetch(descriptor)) ?? []