Dynamic Core Data with SwiftUI Tutorial for iOS

Learn how to take advantage of all the new Core Data features introduced in iOS 15 to make your SwiftUI apps even more powerful. By Mark Struzinski.

4.5 (12) · 5 Reviews

Download materials
Save for later
Share

iOS 15 offers some powerful new features for SwiftUI when used with Core Data. You can now dynamically apply sorts and filters to lists and see the results update immediately. iOS 15 also brings some ease-of-use updates to SwiftUI, such as more straightforward configuration.

In this tutorial, you’ll learn about all the exciting new features released with iOS 15 for SwiftUI apps that use Core Data.

You’ll learn how to:

  • Update existing fetch requests with new sort criteria.
  • Perform sectioned fetch requests.
  • Apply dynamic filters with predicates.

There’s a lot to cover; time to get started!

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.

In this tutorial, you’ll build a Core Data app that helps you track information about your best friends — your besties! Don’t worry if you’re the lonesome type, the app comes with a set of pre-loaded imaginary friends for you to play with.

Open the starter project and take a peek around. The app — called Besties — lets you track your friends’ names, where you met them and the date you met. It uses Core Data for persistence.

Build and run to see the app in action:

Opening screen showing your list of friends

You can tap the + button on the top right to add a new Bestie, if the built-in ones don’t do it for you:

Adding a new friend

After you save, the new Bestie shows up in your list:

New friend in the friends list

You can remove a friend, if they were mean, by swiping to delete, or you can edit one by tapping them, but the main point of this tutorial is to work with the list, so you’ll have a quick tour of the code to orient yourself, then get started.

Getting to know your Besties

Open PersistenceManager.swift. This class handles Core Data setup and save operations. The setup is standard — you initialize an NSPersistentContainer and use that to load the persistent stores. You won’t need to make any changes to this class for the tutorial.

Open AppMain.swift. You’ll see the persistenceManager property is initialized immediately and then passed into the SwiftUI environment for use in child views. This is also the point where, if you don’t have any, the sample friends are added to the database.

Finally, open ContentView.swift. You’ll see a standard implementation of an @FetchRequest property wrapper to fetch a list of Besties sorted by name:

@FetchRequest(
  sortDescriptors: [
    NSSortDescriptor(
      keyPath: \Friend.meetingDate,
      ascending: true)
  ],
  animation: .default)
private var friends: FetchedResults<Friend>

You’ll adapt this view throughout the tutorial to perform dynamic sorting, add search via dynamic predicates and update the list to add sections for organization.

Setting Up Core Data

iOS 15 adds a small convenience with lazy entity resolution. Prior to iOS 15, you would need to set up your Core Data stack before adding it to the environment. If a view attempted to access the view context before setup, you would most likely see a crash. With iOS 15, lazy entity resolution solves that problem. You can remove some boilerplate code because of this.

Open AppMain.swift. Locate and delete the following property near the top of the class:

let persistenceManager = PersistenceManager.shared

Core Data will now lazily initialize entities when needed, so this property doesn’t have to be there to “warm up” the Core Data stack. Update the environment variable in AppMain to the following:

.environment(
  \.managedObjectContext, 
  PersistenceManager.shared.persistentContainer.viewContext)

You are now accessing the view context directly from PersistenceManager and adding it to the environment. This allows ContentView, and all its child views, to use the view context from the .managedObjectContext environment variable.

Build and run, and the app will function just as it did before.

Next, you’ll move on to creating dynamic sorting behavior.

Exploring Dynamic Sorting

Dynamic sorting is another new feature in iOS 15. With dynamic sorts, you can apply new sort descriptors directly to your list of fetched items backed by the @FetchRequest property wrapper. Updating the sort descriptors will cause the view to refresh immediately. You can apply animation by default as well.

Setting Up the Sort Model

First, create a model object representing a single sort. In the Xcode Project navigator on the left sidebar, open the Model group. Create a new Swift file and name it FriendSort.swift.

Add the following after import Foundation:

// 1
struct FriendSort: Hashable, Identifiable {
  // 2
  let id: Int
  // 3
  let name: String
  // 4
  let descriptors: [SortDescriptor<Friend>]
}

This structure defines the following:

  1. Conform to Hashable and Identifiable. This is necessary to use FriendSort in a SwiftUI view for selecting sorts from a menu.
  2. Add id to conform to Identifiable.
  3. name is the friendly name of the sort shown in the sort menu.
  4. descriptors are the SortDescriptors to apply.
Note: Notice the use of the new SortDescriptor object vs. the older NSSortDescriptor. This is new in iOS 15.

Add a list of pre-cooked sort options for the user to choose from, by adding the following static properties to FriendSort:

// 1
static let sorts: [FriendSort] = [
  // 2
  FriendSort(
    id: 0,
    name: "Meeting Place | Ascending",
    // 3
    descriptors: [
      SortDescriptor(\Friend.meetingPlace, order: .forward),
      SortDescriptor(\Friend.name, order: .forward)
    ]),
  FriendSort(
    id: 1,
    name: "Meeting Place | Descending",
    descriptors: [
      SortDescriptor(\Friend.meetingPlace, order: .reverse),
      SortDescriptor(\Friend.name, order: .forward)
    ]),
  FriendSort(
    id: 2,
    name: "Meeting Date | Ascending",
    descriptors: [
      SortDescriptor(\Friend.meetingDate, order: .forward),
      SortDescriptor(\Friend.name, order: .forward)
    ]),
  FriendSort(
    id: 3,
    name: "Meeting Date | Descending",
    descriptors: [
      SortDescriptor(\Friend.meetingDate, order: .reverse),
      SortDescriptor(\Friend.name, order: .forward)
    ])
]

// 4
static var `default`: FriendSort { sorts[0] }

Here’s what’s happening with the code above:

  1. It adds a static property to allow easy access to sorts from a SwiftUI View.
  2. It returns an array of FriendSort items.
  3. Each SortDescriptor specifies the keypath to sort on and the order in which to sort. Note the new SortDescriptor API introduces the .forward and .reverse enumeration values instead of the old Boolean-based sort direction specifier. Each option has a primary sort descriptor and a second one for cases where the first value is the same, such as when you meet lots of people in the same place.
  4. This static property is used as the initial sort option.

Take a moment to update the @FetchRequest property wrapper in ContentView.swift with the newer SortDescriptor API. Find this code at the top in @FetchRequest:

sortDescriptors: [
  NSSortDescriptor(
    keyPath: \Friend.meetingDate,
    ascending: true)
],

Replace it with this:

sortDescriptors: FriendSort.default.descriptors,

Now you are using the sort descriptors from the default sort option.