Dynamically filtering a SwiftData query

Published by malhal on

The built-in @Query does not currently support dynamic changes, instead we can use @DynamicQuery from the SwiftDataX open-source package to achieve it.

The query declaration looks like this:

@DynamicQuery(initialFetchDescriptor: .init(sortBy: \Prospect.name)) var result: Result<[Prospect], Error>

By default that will load all Prospect model objects, sorting them by name. If the fetchDescriptor property is later changed and this View is re-init then the changes are maintained and it will not default back to the initial fetch descriptor.

Next we need a FilterType property that gets passed in:

let filter: FilterType

Since the View could be re-init with a different filter and we need to handle the first time the view appears, we can use onChange as follows:

.onChange(of: filter, initial: true) {
    let showContactedOnly = filter == .contacted
    _result.fetchDescriptor.predicate = #Predicate {
        $0.isContacted == showContactedOnly
    }
}

Another advantage to @DynamicQuery is it takes advantage of Swift’s Result type so the result can either be the valid fetch results or an error which we can read inside body like this:

List {
    switch(result) {
        case .success(prospects):
            ForEach(prospects) { prospect in
                Text(prospect.name)
            }
        case .failure(error):
            Text(error.localizedDescription)
    }
}