Core Data: Beyond the Basics

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

Part 1: Fetching & Displaying Launches

08. Compound 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: 07. Adding Launches to Lists Next episode: 09. Challenge - Adding Tags

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Transcript: 08. Compound Predicates

Instead of working with relationships another way you can fetch the launches for a particular list is by using something you’re already familiar with - predicates.

Open RocketLaunch+CoreDataProperties. You’re going to add another fetch request to fetch only the launches associated with a particular list.

static func launches(in list: RocketLaunchList) -> FetchRequest<RocketLaunch> {}

Go ahead and copy the name and launch date sort descriptor because you want to keep those rules the same.

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

Now for the grand finale you’re going to define a predicate

let listPredicate = NSPredicate(format: "%K == %@", "list.title", list.title!)

So this predicate looks just like the last one you created in that it takes a key path specifier and an object value specifier. Note that the key path is different here - you’re leveraging the relationship you defined earlier. Objective-C key paths are defined as string key paths, and what you’re specifying here is that on each rocket launch object you want to query the list property, and then on that list property you want to query the title property and compare it’s value to the title of the list that you have passed in as an argument.

This way you’re ensuring that only launches that belong to this list will get fetched. Using the title property is not the best you can do - it would be more robust to use a UUID string or an id of some sort but this will suffice.

This isn’t the only predicate you want however - it’d be nice to get rid of that bug where viewed launches weren’t going away, so let’s bring that isViewed predicate in here as well.

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

Unlike sort descriptors where you can just pass many of them in to an array and Core Data figures it all out, you have to be a bit more specific with predicates because a predicate is a filtering condition. You want both of these predicates to be applied together and to do that you’re going to define a third predicate - a compound predicate represented by an instance of NSCompoundPredicate

let combinedPredicate = NSCompoundPredicate()

When you start typing out the initializer you can see there’s quite a few types:

  • an AND predicate, which returns a single predicate by combining all the predicates in the array. This is like using the logical AND operator.
  • a NOT predicate, which inverts the given predicate
  • and then an OR predicate, which like using the logical OR operator, returns a single predicate that evaluates to true if any of the underlying predicates evaluates to true.

Since you want both the list and isViewed predicate to apply you’re going to create an and predicate.

let combinedPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [listPredicate, isViewedPredicate])

Now you can create an instance of the fetch request and return it

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

Navigate back to the LaunchesView. Now instead of assigning a fetch request directly to the stored property, you’re going to set its value in the initializer. Replace the property declaration:

var launchesFetchRequest: FetchRequest<RocketLaunch>

and in the initializer assign the value

init(launchList: RocketLaunchList) {
	self.launchList = launchList
	self.launchesFetchRequest = RocketLaunch.launches(in: launchList)
}

The final step is to replace the launchList.launches value with the launches obtained from the fetched request.

ForEach(launches, id: \.self) { launch in }

Build and run the app. Your app should be working as expected. You can create new lists, add launches to a list and mark them as viewed. In fact, if you tried to mark a previous launch as viewed earlier, it should no longer show up in the list. Awesome job!

Compound predicates are really useful and allow you to concisely express several conditions and let Core Data handle the intersection of them all. In the next video let’s put some of this knowledge to the test.