How to fetch multiple Entities with an NSFetchedResultsController

It’s easy to power a UITableView with Core Data, thanks to the NSFetchedResultsController. To display data, the latter needs an NSFetchRequest. This is great if you’re displaying the same entity over and over again in your table view.

By default and definition however, a fetch request can only ever fetch a single entity from our Core Data stack. What happens if you have two or more entities that you’d like to display in your table view? Technically we’d need multiple fetch requests for that, but how would we add all of them to the fetched results controller?

The answer is: we can’t!

But there is a workaround that may work for your project: we can make use of Entity Inheritance, which will allow us to fetch multiple entities with the same fetch request. This can be a useful timesaver. In this article I’ll explain how this works.

Entity Inheritance only makes sense if your entities are similar in structure. Imagine a flight tracking app in which you have two entities, one for incoming and one for departing flights. Both entities would perhaps have a flight number (NSString) and a time associated with them (NSDate).

 

Let’s build a project

To put this into practice, we can create one entity with these attributes, and then create a second empty entity that inherits from the first. Alternatively we can create one abstract entity with those attributes, and create two child entities that both inherit from the first – either approach will work fine. I’ll stick to the latter option for the remainder of this article.

Let’s create a new project based on the Master/Detail template and inspect the data model. It contains one Event entity that we’ll use. It already contains an NSDate attribute called timeStamp, and I’ll add another one called flightNumber (an NSString).

Screen Shot 2015-09-07 at 11.58.58

We will use this entity in our fetch request to show data in our table view, but we won’t create “events” as such to populate the app. We’ll do that with two new entities that shall be children of this parent entity. Let’s go and create those, and call them Inbound and Outbound. Leave them empty, but select Event as the parent entity on the right hand side (in the Data Model Inspector).

Screen Shot 2015-09-07 at 12.04.03

So far so good! To finish off the data model, let’s create managed object subclasses so we can work with them better in our code. Head over to Editor – Create NSManagedObject Subclass and select all three entities. Xcode is a bit buggy here: make sure you place them inside your project folder, Xcode will always volunteer the folder prior to that.

Screen Shot 2015-09-07 at 12.06.07

Notice also that Xcode will place your new files above the project in the navigator. Apple are good at changing stuff without notice, but they’re not so good at fixing things (as this example shows). Simply highlight the classes and drag them into your main project’s group. Your project navigator should look like this – just to avoid any nasty error messages down the line:

Screen Shot 2015-09-07 at 12.50.20

 

Coding the interface

I’m going to keep it super simple here and leave most of the code from the template as it is. I will however create two empty methods to insert an inbound and an outbound flight to the app. We already have a plus icon at the top right that will insert an Event item. We’ll use the same location to create an outbound flight, and on the left hand side we’ll add one to create an inbound flight.

Before we begin, we’ll import all necessary header files into MasterViewController.h:

#import <UIKit/UIKit.h>
#import <CoreData/CoreData>
#import "Event.h"
#import "Inbound.h"
#import "Outbound.h"
#import "AppDelegate.h"

In addition to our three generated subclasses I’m also importing AppDelegate.h so that I can call a convenient method that will save our managed object context for us.

Next, in the MasterViewController.m file, we’ll add two methods that will create a dummy object each and inter it into the managed object context.

 

- (void)insertInbound {
    
    // create an Inbound object
    Inbound *newFlight = [NSEntityDescription insertNewObjectForEntityForName:@"Inbound" inManagedObjectContext:self.managedObjectContext];
    newFlight.timeStamp = [NSDate date];
    newFlight.flightNumber = @"Inbound Flight";
    
    // save the context (using AppDelegate method)
    AppDelegate *myAppDelegate = [UIApplication sharedApplication].delegate;
    [myAppDelegate saveContext];
}

- (void)insertOutbound {
    
    // create an Outbound object
    Outbound *newFlight = [NSEntityDescription insertNewObjectForEntityForName:@"Outbound" inManagedObjectContext:self.managedObjectContext];
    newFlight.timeStamp = [NSDate date];
    newFlight.flightNumber = @"Outbound Flight";
    
    // save the context (using AppDelegate method)
    AppDelegate *myAppDelegate = [UIApplication sharedApplication].delegate;
    [myAppDelegate saveContext];
}

When an object is created, it is populated with dummy values here: a date and time of “right now” and a simple title that shows what type of flight has been added. Obviously in a real app, this is where the actual data would be added. As soon as we save the context, our objects are stored (we’ll do that via the conveniently provided method in our AppDelegate).

We shall call the above methods with two buttons at the top left and right. Rather than add those with connections in the storyboard, we’ll do it like Apple did in the template and create those button objects in code. Apple does it in viewDidLoad, which currently looks like this:

 

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // Do any additional setup after loading the view, typically from a nib.
    self.navigationItem.leftBarButtonItem = self.editButtonItem;

    UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(insertNewObject:)];
    self.navigationItem.rightBarButtonItem = addButton;
    
    self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
}

 

I’ll remove the edit button on the left and replace it with my own instances, each of which will call the insert methods via a selector. Here’s the updated version for our demo app:

 

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // left button
    UIBarButtonItem *inboundButton = [[UIBarButtonItem alloc]initWithTitle:@"In" style:UIBarButtonItemStyleDone target:self action:@selector(insertInbound)];
    self.navigationItem.leftBarButtonItem = inboundButton;
    
    // right button
    UIBarButtonItem *outboundButton = [[UIBarButtonItem alloc]initWithTitle:@"Out" style:UIBarButtonItemStyleDone target:self action:@selector(insertOutbound)];
    self.navigationItem.rightBarButtonItem = outboundButton;
    
    self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
}

 

Displaying our data

Try running the app: it should work fine and insert a new timestamp with either button. But all we see is the date right now. Let’s fix this by entering our Main.storyboard. Find the table view cell, select it and change its style from Basic to Subtitle. This will display two lines of text. Feel free to change the word Master into something more appropriate (perhaps Flight Tracker).

Screen Shot 2015-09-07 at 12.53.17

Now we’ll head back to MasterViewController.m and populate those two lines properly. There’s a method called configureCell. It currently looks like this:

 

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = [[object valueForKey:@"timeStamp"] description];
}

 

Let’s zoosh it up a bit and replace it with this:

 

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    
    // grab the current object
    Event *currentFlight = [self.fetchedResultsController objectAtIndexPath:indexPath];
    
    // show the title
    cell.textLabel.text = currentFlight.flightNumber;
    
    // format the date
    NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
    formatter.dateStyle = NSDateFormatterMediumStyle;
    formatter.timeStyle = NSDateFormatterMediumStyle;
    
    // show the flight time
    cell.detailTextLabel.text = [formatter stringFromDate:currentFlight.timeStamp];
}

And there we have it: both our entities are displayed in the same table view.

Result!

iOS Simulator Screen Shot 7 Sep 2015 13.02.41

 

Extra Credit: Tinting cells by testing the entity type

There’s just one more thing: we may want to set inbound and outbound flights apart visually by tinting the cell background depending on what type of entity is displayed. For example, inbound flights could be shown as green, while outbound flights could be shown in blue.

We can do this by testing which class the current Event object belongs to. Add this code to the bottom of the configureCell method we’ve seen above:

 

    // colour the cell background by testing the class
    UIColor *green = [UIColor colorWithRed:0.8 green:1.0 blue:0.8 alpha:1];
    UIColor *blue = [UIColor colorWithRed:0.8 green:0.8 blue:1.0 alpha:1];
    
    if ([currentFlight isKindOfClass:[Inbound class]]) {
        cell.backgroundColor = green;
    } else {
        cell.backgroundColor = blue;
    }

iOS Simulator Screen Shot 7 Sep 2015 18.00.39

And there we have it!

Entity Inheritance behaves much like Class Inheritance: whatever attributes the parent entity had, the children will inherit without us having to create the same attributes again. The same goes for any code we write in the parent subclass. Obviously Entity Inheritance only makes sense if your data is similar, as it is in the flight tracker example, or perhaps in a personnel database.

 

Demo Project

I’ve got a working demo project on GitHub. It’s a fully working version of what I’ve explained in this article. Feel free to examine it:





3 thoughts on “How to fetch multiple Entities with an NSFetchedResultsController

    1. Hi Kieran, the principle of Entity Inheritance is exactly the same in Swift. Sadly I don’t know enough Swift at the moment to update the demo project, but if you follow my instructions and start from the Swift based Master-Detail template, you should be able to build the same thing in no time.

Leave a Reply