— Swift, iOS, SwiftData, Data Filtering — 2 min read
I've kind of been on a roll lately with Swift articles. Today we're going to take a look at filtering objects with SwiftData. SwiftData provides a robust way to filter data in Swift apps: using the new powerful #Predicate
macro. Let's jump in and take a look at at!
SwiftData predicates allow you to filter data loaded through various operations like @Query
, custom FetchDescriptor
configurations, and deletion of model objects. The fundamental concept behind these predicates is straightforward: define a condition that each data model object must meet to be included in the final result set. This takes over from the old NSPredicate
way of doing things.
For the purposes of the example imagine you have a Note
model in your application:
1@Model2final class Note {3 var text: String4 var created: Date5 6 init(7 text: String = "",8 created: Date = Date()9 ) {10 self.text = text11 self.created = created12 }13}
You can use SwiftData predicates to filter based on certain criteria. Here's how you could filter for notes that were created before 'now'.
1static var date: Date {2 Date.now3}4
5@Query(filter: #Predicate<Note> { note in6 note.created < date7}) var notes: [Note]
The basic idea here is that whatever you add in the #Predicate macro returns a true(or false). Obviously the items that end up being false are filtered out and the ones that are true get included.
SwiftData also supports various string methods for filtering. You could find all the notes that contain specific text like this:
1@Query(filter: #Predicate<Note> { note in2 note.text.contains("foo")3}) var notes: [Note]
The result of this is that you would only get back the notes that contain "foo" somewhere in the text.
Like I wrote above, as long as your logic boils down to returning a Boolean you are good to go. So a combination of the two filters above could look something like this if you have more complicated logic:
1static var date: Date {2 Date.now3}4
5@Query(filter: #Predicate<Note> { note in6 if note.text.contains("foo") {7 return note.created < date8 } else {9 return true10 }11}) var notes: [Note]
The above does a check to see if text
contains 'foo'. If it does it checks that created was earlier than now. If it doesn't it returns true so any notes without 'foo' in the text get included without any checks. The sky realy is the limit...
#Predicate doesn't seem to handle filtering for optionals well... Or maybe what I encountered personally was a bug. SwiftData
is quite new and does have some bugs - you can easily run into some with a quick Google Search. I'll talk about this in a future post and how I was able to work around it. That's enough for today though! I hope you're enjoying using SwiftData as much as I am. I definitely prefer it to the old NSPredicate
approach.