Dynamically filtering a SwiftData query
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)
}
}