Core Data: Beyond the Basics

Jul 26 2022 · Swift 5.5, iOS 15, Xcode 13.3.1

Part 1: Fetching & Displaying Launches

04. Filtering Using Predicates

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 03. Sorting Data Using Sort Descriptors Next episode: 05. Dynamically Adjust Sort Descriptors
Transcript: 04. Filtering Using Predicates

You’ve learned quite a bit so far - you know how to save launches to the persistent store, fetch them to display and sort them by properties. But you might have noticed a pretty big flaw in your RocketLaunches app. Build and run the app. Now try tapping on any of the RocketLaunches, mark it as viewed (which sets the isViewed property to true), and then return to the list.

You’ll notice that the view changes - the checkmark circle is filled but the RocketLaunch doesn’t actually go away to indicate that it’s been viewed.

This behavior occurs because the app is currently fetching all launches in the database, regardless of whether they have been marked as viewed or not.

To fix this we need to only fetch launches that have an isViewed value of false and to do that you need predicates. A predicate allows you to narrow down the objects returned in a fetch request by applying a filter and is represented by the NSPredicate class. Just like you did with the sort descriptors you’re going to add a new fetch request to the extension of RocketLaunch.

static func unViewedLaunchesFetchRequest() -> FetchRequest<RocketLaunch> {}

You’ll use the same sort descriptors as before, one for the name and another for launch date

let nameSortDescriptor = NSSortDescriptor(key: "name", ascending: true)
let launchDateSortDescriptor = NSSortDescriptor(key: "launchDate", ascending: false)

Next you’re going to define a predicate that filters out all launches that have been viewed and you’ll do this by creating an instance of NSPredicate

let isViewedPredicate = NSPredicate

NSPredicate is a really powerful class and you can do a lot with it but it’s not apparent from the initializers. You’re going to select the one that takes a format and arguments. For the format argument you’re going to define a string. Don’t worry about understanding the string just yet, I’ll get to that in a second.

let isViewedPredicate = NSPredicate(format: "%K == %@",)

And for the arguments you’re going to specify two: First a string, and second an instance of NSNumber.

let isViewedPredicate = NSPredicate(format: "%K == %@", "isViewed", NSNumber(value: false))

So what’s going on here? NSPredicate accepts a format string as one of its core arguments and this format string has a language of its own. Under the hood there is a predicate parser that breaks this string down and converts it into a set of rules. Predicate strings are a really complex topic and we’re not even going to scratch the surface in this course, but let’s talk about some of the important basics.

The %K and %@ symbols are known as format specifiers and they are two very important ones. %K as the docs say, is a var arg substitution for a key path. What this means is that when reading this string, the predicate parser is going to substitute a key path in place. Like the sort descriptor the key path you’re specifying here is the property you want to apply the filter rule on.

Going back to the initializer it took two arguments - the first was the format string and the second was a variadic series of arguments. Variadic arguments mean you can pass in as many as you want and they’re supplied to the function in an array. When the predicate parser parses the format string and finds a format specifier it replaces it with the corresponding argument from the arguments list.

Since the key path specifier was first in the format string, it substitutes it with the first argument provided in the argument list. Evaluating this your format string starts by saying apply a filter on the isViewed property. Let’s keep going.

Next up in the format string is a double equals sign. Format strings support all the common comparison operators and here you’re indicating that you want the left hand expression to equal the right hand expression.

On the right hand you have another format specifier - %@. Where %K was a var arg substitution for a key path, %@ is a var arg substitution for an object value. So just like before the parser encounters this second format specifier and substitutes it with the second argument in the args list, except this time that’s an actual value.

Your end goal is to filter out any launches that have been viewed which means only returning launches where isViewed is set to false. False is not an object value though so you wrap this in an instance of NSNumber.

It’s important to understand how predicates work. Even though I said these were substitutions of values, it’s not like Swift’s string interpolation syntax where the resulting string is isViewed == false.

Which format specifier you use and and where makes all the difference. If you were to swap the order it would fail. A %K, or key path specifier, tells Core Data that the value substituted here is a property on the fetched result and that the value of the property should equal the one specified using the object value specifier.

Now that you have a predicate you can create a fetch request and return it.

return FetchRequest(entity: RocketLaunch.entity(), sortDescriptors: [nameSortDescriptor, launchDateSortDescriptor], predicate: isViewedPredicate)

Navigate back to LaunchesView and swap out the fetch request with the new one.

let launchesFetchRequest = RocketLaunch.unViewedLaunchesFetchRequest()

Build and run the app. Now you can mark the launches as viewed in the detail, and when you return to the list, those don’t appear anymore. How awesome is that?

Predicates are a really powerful feature and there’s a lot you can achieve with concise predicate syntax. But we’ll stop here for now. In the next video, let’s take a quick detour and talk about relationships.