Skip to content

SwiftData Predicates and Filtering Data

Swift, iOS, SwiftData, Data Filtering2 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!

Understanding the Basics of SwiftData Predicates

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.

Filtering Based on Dates

For the purposes of the example imagine you have a Note model in your application:

2final class Note {
3 var text: String
4 var created: Date
6 init(
7 text: String = "",
8 created: Date = Date()
9 ) {
10 self.text = text
11 self.created = created
12 }

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 {
5@Query(filter: #Predicate<Note> { note in
6 note.created < date
7}) 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.

String Related Stuff

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 in
2 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.

Getting More Complicated

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 {
5@Query(filter: #Predicate<Note> { note in
6 if note.text.contains("foo") {
7 return note.created < date
8 } else {
9 return true
10 }
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...

Getting Sidetracked: Handling Optional Values

#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.