Displaying Data With NSFetchedResultsController and Swift

The combination of an NSFetchedResultsController and a UITableView provides a powerful way to integrate Core Data with a user interface. The greatest benefits of using NSFetchedResultsController come when we use it to automatically update a table view when objects are added, updated, or removed from a Core Data data store. First things first, though…

With a Core Data data store seeded with data, the next logical step is to display that data somewhere other than the console. This post will be devoted to figuring out how to set up an NSFetchedResultsController to display data inside a UITableView.

A follow-up post has been published to help you keep the table view in sync with the data as it changes in your persistent store, so once you’re finished here, you might check out that next step!

Final goal

NSFetchedResultsController will help us accomplish two things:

  1. It will fetch data from the Core Data data store
  2. It will use some of the data we fetch to populate various pieces of the UI (table view section headers, cell “title” and “subtitle” text. Here’s what we’re going for:

Zootastic TableView FinalDisplay

Setup and resources

I’m continuing my “Zootastic” example that I used to write about using Swift to seed a Core Data database. In fact, I’ve simply branched the project on GibHub and added the things we’re exploring in this post.

For this entry, we’ll still be dealing with our three primary NSManagedObject subclasses: Zoo, Animal, And Classification:

Zootastic XCdatamodel

Storyboard

Zootastic is a single view application. The Storyboard contains one view controller with a table view filling the Scene.

Zootastic Storyboard TableView

The table view is using one prototype cell with an Identifier of “Cell” (for simplicity). The Style of the prototype cell is set to Subtitle.

Zootastic Storyboard TableViewCell

MainViewController.swift

MainViewController.swift is where the action is happening. Here’s a quick outline of what we need to accomplish in this class:

1
2
3
4
5
6
7
8
public class MainViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {

    // maintain a reference to NSManagedObjectContext instance

    // create and configure an NSFetchedResultsController instance

    // Implement UITableViewDataSource methods
}
As you can see, MainViewController is a class concerned with being the table view’s data source and delegate. Additionally, it will serve as the NSFetchedResultsControllerDelegate. For this post, we won’t actually need the fetched results controller delegate functionality to display data. Those methods are particularly useful for synchronizing things when data changes.

With the class declaration out of the way, we’ll investigate the class implementation one section at a time.

Maintain NSManagedObjectContext instance reference

1
2
3
4
5
6
public class MainViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
    
    public var context: NSManagedObjectContext!

    // ...
}
You may be asking, “Where will you set this NSManagedObjectContext reference?”. I’m employing a pattern that I’ve found successful in the past: I assign it when the finishes launching through the AppDelegate’s application:didFinishLaunchingWithOptions method. More on this, shortly

For now, know that we’re counting on that later step to take place, since context is defined as an implicitly unwrapped optional.

Create and configure NSFetchedResultsController instance

Next, we need to create and configure an NSFetchedResultsController instance. Here’s a bit of code with comments to follow:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MainViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
    
    // ...
    
    lazy var fetchedResultsController: NSFetchedResultsController = {
        let animalsFetchRequest = NSFetchRequest(entityName: "Animal")
        let primarySortDescriptor = NSSortDescriptor(key: "classification.order", ascending: true)
        let secondarySortDescriptor = NSSortDescriptor(key: "commonName", ascending: true)
        animalsFetchRequest.sortDescriptors = [primarySortDescriptor, secondarySortDescriptor]
        
        let frc = NSFetchedResultsController(
            fetchRequest: animalsFetchRequest,
            managedObjectContext: self.context,
            sectionNameKeyPath: "classification.order",
            cacheName: nil)
        
        frc.delegate = self
        
        return frc
        }()
    
    // ...

}
If you’ve not read Colin Eberhardt’s Swift Initialization and the Pain of Optionals post, I highly recommend it. His post is a fantastic analysis, and the final option of using lazy stored properties initialized by a closure expression is what I’ve chosen to do here. I won’t repeat his analysis here, so feel free to jump over to his post to figure out what’s going on there.

Within the closure expression, I’m setting up a fetch request with some sorting applied. All that’s left is to initialize the NSFetchedResultsController, set its delegate and return it.

viewDidLoad()

Once the view has loaded, the idea is to perform the NSFetchedResultsController instance’s fetch request so that it has data to use in our UITableViewDataSource methods. This is how to do it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class MainViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {

    // ...

    override public func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

            do {
                try fetchedResultsController.performFetch()
            } catch {
                print("An error occurred")
            }
    }

    // ...

}

UITableViewDataSource methods

The final step in implementing MainViewController is to set up the table view so that it pulls data from fetchedResultsController. I’m implementing the standard UITableViewDataSource methods here, along with tableView:titleForHeaderInSection. Take a look:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class MainViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {

    // ...

    // MARK: TableView Data Source
    public func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        if let sections = fetchedResultsController.sections {
            return sections.count
        }
        
        return 0
    }
    
    public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if let sections = fetchedResultsController.sections {
            let currentSection = sections[section] 
            return currentSection.numberOfObjects
        }
        
        return 0
    }
    
    public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) 
        let animal = fetchedResultsController.objectAtIndexPath(indexPath) as! Animal
        
        cell.textLabel?.text = animal.commonName
        cell.detailTextLabel?.text = animal.habitat
        
        return cell
    }
    
    public func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        if let sections = fetchedResultsController.sections {
            let currentSection = sections[section] 
            return currentSection.name
        }
        
        return nil
    }

    // ...

}

Apart from a bit of if let ___ = ___ syntax, there’s not an awful lot of surprising code here if you’re familiar with working with table views. I’ve highlighted the relevant code related to fetchedResultsController. Without using NSFetchedResultsController, you’d probably supply data to the table view from an array or a dictionary or both. The fetchedResultsController code simplifies the data display dilemma when you’re using Core Data.

Once the UITableViewDataSource methods are implemented, the implementation of MainViewController is complete for this example.

AppDelegate.swift

There’s one final thing we need to do in order to get things rolling. In the “maintain NSManagedObjectContext instance reference” section of this post, I mentioned the strategy for assigning the NSManagedObjectContext instance in the MainViewController. Here’s how I do it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class AppDelegate: UIResponder, UIApplicationDelegate {

    // ...


    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        
        let dataHelper = DataHelper(context: self.managedObjectContext)
        dataHelper.seedDataStore()
        
        dataHelper.printAllZoos()
        dataHelper.printAllClassifications()
        dataHelper.printAllAnimals()
        
        let rootViewController = self.window!.rootViewController as! MainViewController
        rootViewController.context = self.managedObjectContext;
        
        return true
    }

    // ...

}

Note: With iOS 13, the code to assign the managedObjectContext to the root view controller needs to go in your app’s SceneDelegate.

The portion new to “injecting” the managedObjectContext into MainViewController is highlighted. I simply grab a reference to the rootViewController (which in our example is the MainViewController) and cast it to the appropriate type. Then I set the context property to the managedObjectContext that’s created in the AppDelegate via Xcode’s auto-generated Core Data stack setup.

Wrapping up

If you’re using Core Data in your iOS application, the combination of an NSFetchedResultsController and a UITableView provides a powerful way to integrate data from your data store into your UI. We’ve explored how to display data in a table view using NSFetchedResultsController. Feel free to grab the GibHub project for further investigation and to see Zootastic in action.

comments powered by Disqus