Core Data: Beyond the Basics

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

Part 1: Fetching & Displaying Launches

03. Sorting Data Using Sort Descriptors

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: 02. Displaying Launches Next episode: 04. Filtering Using Predicates
Transcript: 03. Sorting Data Using Sort Descriptors

In the last video you were able to fetch all the RocketLaunches from the persistent store but you didn’t have a way to sort the incoming data. If you create a new RocketLaunch it’s simply added to the top of the list.

What if you wanted to display the soonest rocket launch at the top of the list and later RocketLaunches at the bottom? For that you can use a sort descriptor represented by the NSSortDescriptor class.

A sort descriptor is a description of how you want to order a collection. Open RocketLaunch+CoreDataProperties.swift. Earlier you defined a fetch request to get all rocket launches from the persistent store. The initializer for the property wrapper took an array of sort descriptors and you just passed in an empty array indicating that you didn’t want the data sorted in any way.

You could amend this method but you might need to reuse this elsewhere so let’s define a new method that returns a sorted fetch request.

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

Next you need to define a sort descriptor. A sort descriptor takes the key path of the property you want to sort against and a sort order. Let’s define one to sort on the launch date.

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

Here you’ve created an instance of NSSortDescriptor, and specified launchDate as the key path to the property you want to sort on. Remember, these are Objective-C types which is why you’re defining string key paths as opposed to Swift’s typed key paths. Passing in true for the second argument indicates that you want the first date to be the smallest value.

You can now use this sort descriptor when defining a fetch request.

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

Back in LaunchesView.swift you can replace the basicFetchRequest() with the sorted fetch request you created.

let launchesFetchRequest = RocketLaunch.sortedFetchRequest()

Let’s take this for a spin. Build and run the app. You should see the 2 launches that you added before. If you add another RocketLaunch with a later date than the last one on the list, it is now added at the bottom of the list instead of the top; if you add one with a launch date between the first two, it gets sorted into the correct location in the list.

Sort descriptors, like the simple one you just added, are fairly easy to write and allow you to define sort logic in a declarative manner - stating how you want the results sorted instead of writing out the actual sort logic; it’s called a sort descriptor for a reason.

You can do more than simple, single property sorts. For example let’s say you wanted to sort by title and then by launch date. Add another function to define a third fetch request

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

Next add a sort descriptor to sort by name:

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

I’ve set ascending to true but you can set it to false if you like. As an aside you might be thinking, what if I wanted more fine grained sorting on the title. The sort descriptor initializer takes an optional third argument, a closure that allows you to return a ComparisonResult to sort on.

You already have the date sort descriptor from the previous function, so you can copy that down here:

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

You can now define a fetch request using these sort descriptors. Remember the initializer takes an array of sort descriptors so you can apply both of them.

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

The order you specify these is important. By listing the name sort descriptor first, and then the launch date, you’re specifying that you are sorting the launches by name first. Then for cases where the title is the same you want to order by launch date.

Let’s try this out. In LaunchesView swap out the fetch request with the latest one.

var launchesFetchRequest = RocketLaunch.fetchRequestSortedByNameAndLaunchDate() 

Build and run the app. You should see the launches sorted in alphabetical order. To test whether both sort descriptors are working let’s add one more launch. Set the name to “A night time launch!” and give it a date that is earlier than the current launch with that name.

A night time launch!
Date: 4/30/22

With 2 identically named launches, it’s hard to tell which is which. Luckily there is a detail view we can point each row to in order to find out the launch date. In LaunchesView.swift, wrap the content of the HStack in a NavigationLink that points to a LaunchDetailView

NavigationLink(destination: LaunchDetailView(launch: launch)) {
	LaunchStatusView(isViewed: launch.isViewed)
	Text("\(launch.name ?? "")")
}

Build and run the app again. Now you can click on each row.

The new launch you added gets sorted by name correctly - alongside the existing night time launch - but also gets sorted before the existing night time launch, since it launches the night before.

Sort descriptors are straightforward to understand and offer a simple but powerful way to sort the data fetched from the data store. In the next video let’s talk about predicates.