Introduction
In this Part 4 of Core Data Basics I’ll demonstrate how to implement and use Relationships. As we will be modifying an existing Core Data Model I will introduce Data Model Versioning. The Tab Bar Controller will also be introduced to manage our growing Storyboard
Prerequisites
We follow on from Part 3 where we’ve already created a simple Storyboard app with delegation that Adds, Views, Edits and Deletes Core Data. Download the project from the end of Part 3 then extract and open it with Xcode.
Data Model Versioning
In Part 2 we created a simple Data Model that contained one entity called Role which contained a single attribute called name. To explain relationships I’ll need to update the Data Model. Remember this: Any time you intend to release an updated Data Model you should create a new version of the Data Model before you edit it.
To add a new version of the existing Data Model select Model.xcdatamodeld then click Editor > Add Model Version... Accept the default name of Model 2 then click Finish:

You will now have two versions of the Core Data Model so set the active version to Model 2 using File Inspector while Model.xcdatamodeld is selected:

The green tick should now be on Model 2:

To begin modifying the new version select Model 2.xcdatamodel then click Add Entity. Create a new Person entity with two attributes firstname and surname with a type of String:

Change Editor Style to Graph by toggling this little button:

You may need to drag the two entities apart to see them properly like this:

Configuring Relationships
To ask Core Data what Role a person is in (or what persons are in a role) we will need a relationship. Consider the following scenario:
A person (Tim Roadley) is in a role (Project Manager). On the flip side the role (Project Manager) is held by multiple people (Tim Roadley, Joe Blogs and Fred Random). You can see that those two points of view on the same relationship need different names – inRole and heldBy. In addition to that it’s a one to many relationship as (Tim Roadley) can’t be in two roles at once… well, at least not in my model. Create a relationship between the two entities by holding down Control and dragging a line between them. Rename the newRelationship’s as follows:

Using Data Model Inspector edit heldBy so it is a To-Many Relationship:

You will now notice the double arrow indicating what direction the To-Many relationship is in:

Now that our Data Model has been updated we need to generate the Person NSManagedObject sub-class and regenerate the Role NSManagedObject sub-class. Select both the Person and Role entities, click Editor > Create NSManagedObject Subclass… then Create. Replace the existing Role files.
Inspect both Role and Person classes. There is the potential that one of them might not know about the other. To be on the safe side just regenerate both classes all over again making sure to overwrite the existing four files:

Run the app and it will crash with the error ‘The model used to open the store is incompatible with the one used to create the store‘ … awesome! We need to update the AppDelegate to put some extra options on the Persistent Store Coordinator.
Find this line of code in the persistentStoreCoordinator method of AppDelegate.m:
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) |
Change it to this:
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) |
What that does is enable the automatic migration of old Data Models to the latest version! You can read more about it here. Run the app again and it should not crash.
Re-using Hard Work
People Classes
We’ve already learned how to configure Add, View, Edit and Delete Role Table View Controllers in previous parts of the tutorial. We need the same Table View Controllers for Person so why double up on work? Remember that:
- RolesTVC forms a perfect template for PersonsTVC
- AddRoleTVC forms a perfect template for AddPersonTVC
- RoleDetailTVC forms a perfect template for PersonDetailTVC
We may as well leverage search and replace to create the Person classes. Create a new PersonsTVC, AddPersonTVC and PersonDetailTVC class. It doesn’t matter how you create the classes as we replace their entire contents. You should now have 6 new Person TVC files in the project. You may want to start organising the folder structure a little too:

Time for some boring stuff:
- Copy all the code from RolesTVC.h to PersonsTVC.h
- Copy all the code from RolesTVC.m to PersonsTVC.m
- Copy all the code from AddRoleTVC.h to AddPersonsTVC.h
- Copy all the code from AddRoleTVC.m to AddPersonsTVC.m
- Copy all the code from RoleDetailTVC.h to PersonDetailTVC.h
- Copy all the code from RoleDetailTVC.m to PersonDetailTVC.m
- Replace the word role with the word person in all of the new Person class files.
- Replace the word Role with the word Person in all of the new Person class files.
Notice the lower/titlecase of role/Role and person/Person. It’s critical you match case when doing the search and replace. Just click the little magnifying glass so you can then tick the Match Case option:

After all that you should be left with a few errors to do with references to the name attribute in the Person entity which doesn’t exist. For each of those errors you can change name to firstname and make most of them go away.
As a part of that work change the sort descriptor in the setupFetchedResultsController method of PersonsTVC.m from name to firstname:
// 4 - Sort it if you want
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"firstname" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)]]; |
Comment out the following lines in AddPersonTVC.h:
//@property (strong, nonatomic) IBOutlet UITextField *personNameTextField; |
Comment out the following lines in AddPersonTVC.m:
//@synthesize personNameTextField;
//[self setPersonNameTextField:nil];
//person.firstname = personNameTextField.text; |
Comment out the following lines in PersonDetailTVC.h:
//@property (strong, nonatomic) IBOutlet UITextField *personNameTextField; |
Comment out the following lines in PersonDetailTVC.m
//@synthesize personNameTextField;
//self.personNameTextField.text = self.person.name;
//[self setPersonNameTextField:nil];
//[self.person setName:personNameTextField.text]; |
Run the app to ensure all the errors are gone. Just in case you got a bit lost here’s a copy of the code so far.
People Views
Open MainStoryboard.storyboard and duplicate everything on it by pressing Command-A then Command-D. Move the copied stuff up the top so it looks like the picture below:

We will reconfigure the stuff up the top to become the People views. You should know how to do this next stuff by now, so, for the copied views only perform the following:
- Rename Roles Navigation Item Title to People
- Rename Add Role Navigation Item Title to Add Person
- Rename Role Detail Navigation Item Title to Person Detail
- Set the Reuse Identifier of the People Prototype Cell to Persons Cell
- Rename Add Person Table View Section Header from Role Name to First Name
- Copy/Paste the Table View Section - First Name into its containing Table View.

- Rename copied Table View Section Header from First Name to Surname
- Rename Person Detail Table View Section Header from Role Name to First Name
- Copy/Paste the Table View Section - First Name into its containing Table View.

- Rename copied Table View Section Header from First Name to Surname
- Rename the Segue between People and Add Person to Add Person Segue
- Rename the Segue between People and Person Detail to Person Detail Segue
- Set the Class of the People Table View Controller to PersonsTVC
- Set the Class of the Add Person Table View Controller to AddPersonTVC
- Set the Class of the Person Detail Table View Controller to PersonDetailTVC
The Person views should new resemble this:
The
First Name and
Surname Text Fields need to be linked to the underlying classes of the views they’re found on (
AddPersonTVC.h and
PersonDetailTVC.h). Use the same procedure used in
Part 2 to link this up. As a reminder swap to Assistant Editor then ensure you’re viewing the correct underlying class (see image below for how to change). Hold
Control and drag from each Text field to the to the respective class header, call the new properties
personFirstnameTextField and
personSurnameTextField. If you were successful you will have created two new properties with little grey dots to the left of them that indicate a connection to a view. Don’t forget you need to do this for both
AddPersonTVC.h and
PersonDetailTVC.h:
Add the following code to the save method of AddPersonTVC.m under the commented out lines:
person.firstname = personFirstnameTextField.text;
person.surname = personSurnameTextField.text; |
Add the following code to the viewDidLoad method of PersonDetailTVC.m under the commented out lines:
self.personFirstnameTextField.text = self.person.firstname;
self.personSurnameTextField.text = self.person.surname; |
Add the following code to the save method of PersonDetailTVC.m under the commented out lines:
self.person.firstname = self.personFirstnameTextField.text;
self.person.surname = self.personSurnameTextField.text; |
Remove the old references to roleNameTextField from the Outlets of the Add Person and Person Details Table View Controllers:

Edit the // Configure the cell … code in cellForRowAtIndexPath method of PersonsTVC.m:
// Configure the cell...
Person *person = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSString *fullname = [NSString stringWithFormat:@"%@ %@", person.firstname, person.surname];
cell.textLabel.text = fullname;
//cell.textLabel.text = person.firstname; |
Tab Bar Controllers
Now it’s time to bring it all together with a Tab Bar Controller so drag one on to the canvas to the left of the existing stuff. Delete the two views that come with it. Hold down Control and drag a line to both existing Navigation controllers to create a viewControllers relationship Segue. Set the Tab Bar Controller as the initial view controller so it is the first view shown when the app launches (you may need to un-tick one of the Navigation Controllers as also being the initial view controller. At a very high level the canvas should look like this:
On each Navigation Controller double click the little icon to set the Title of each Bar Item. Call one Roles and the other People. If you look at the Tab Bar Controller it will now show these names automatically. It’s important to note the order from left to right as 0,1 .. etc as we will be referring to this index in code soon:
One of the final things to do now is to configure the AppDelegate.m to pass the managed object context through to each of the view controllers’ managedObjectContext properties. Without this we will break Core Data. Ensure the following code is in AppDelegate.m:
#import "PersonsTVC.h"
#import "RolesTVC.h" |
Edit the didFinishLaunchingWithOptions method of the AppDelegate.m as follows:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// The Tab Bar
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
// The Two Navigation Controllers attached to the Tab Bar (At Tab Bar Indexes 0 and 1)
UINavigationController *personsTVCnav = [[tabBarController viewControllers] objectAtIndex:0];
UINavigationController *rolesTVCnav = [[tabBarController viewControllers] objectAtIndex:1];
// The Persons Table View Controller (First Nav Controller Index 0)
PersonsTVC *personsTVC = [[personsTVCnav viewControllers] objectAtIndex:0];
personsTVC.managedObjectContext = self.managedObjectContext;
// The Roles Table View Controller (Second Nav Controller Index 0)
RolesTVC *rolesTVC = [[rolesTVCnav viewControllers] objectAtIndex:0];
rolesTVC.managedObjectContext = self.managedObjectContext;
//NOTE: Be very careful to change these indexes if you change the tab order
// The following stuff was commented out since we're using a Tab Bar Controller
//UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
//RolesTVC *controller = (RolesTVC *)navigationController.topViewController;
//controller.managedObjectContext = self.managedObjectContext;
return YES;
} |
You should now be able to run the app and tab switch between Adding, Viewing, Editing and Deleting both People and Roles!
Relationships in Action
The goal of my example relationship is to show people’s roles on the People table. To show more than one data value on the People table, change the Table View Cell Style of the People Prototype Cells to Subtitle:

To associate a person with a role I was going to use a UIPickerView however found that it is quite an art to get one of those displaying properly with Core Data on all devices. I will save that for another tutorial and for now settle on creating a Person’s Role Table View Controller backed by a new class called PersonRoleTVC. PersonRoleTVC uses techniques you should by now be used to from previous parts of this tutorial such as passing a context and delegation. Create a new PersonRoleTVC class then replace all the code in PersonRoleTVC.h with the following:
#import <UIKit/UIKit.h>
#import "Role.h"
#import "CoreDataTableViewController.h" // so we can fetch
@class PersonRoleTVC;
@protocol PersonRoleTVCDelegate
- (void)roleWasSelectedOnPersonRoleTVC:(PersonRoleTVC *)controller;
@end
@interface PersonRoleTVC : CoreDataTableViewController
@property (nonatomic, weak) id delegate;
@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
@property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (strong, nonatomic) Role *selectedRole;
@end |
Also replace all of the code in PersonRoleTVC.m with the following:
#import "PersonRoleTVC.h"
@implementation PersonRoleTVC
@synthesize fetchedResultsController = __fetchedResultsController;
@synthesize managedObjectContext = __managedObjectContext;
@synthesize delegate;
@synthesize selectedRole;
- (void)setupFetchedResultsController
{
// 1 - Decide what Entity you want
NSString *entityName = @"Role"; // Put your entity name here
NSLog(@"Setting up a Fetched Results Controller for the Entity named %@", entityName);
// 2 - Request that Entity
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
// 3 - Filter it if you want
//request.predicate = [NSPredicate predicateWithFormat:@"Person.name = Blah"];
// 4 - Sort it if you want
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name"
ascending:YES
selector:@selector(localizedCaseInsensitiveCompare:)]];
// 5 - Fetch it
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
[self performFetch];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setupFetchedResultsController];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
self.selectedRole = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSLog(@"The PersonRoleTVC reports that the %@ role was selected", self.selectedRole.name);
[self.delegate roleWasSelectedOnPersonRoleTVC:self];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Person Role Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
Role *role = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = role.name;
return cell;
}
@end |
Open up MainStoryboard.storyboard then:
- On the Person Detail Table View Controller copy/paste the Surname Table View Section into the same Table View.
- Rename the copied Table View Section to Role then delete the UITextField within it.
- Drag a new Table View Controller to the canvas to the right of the Person Detail Table View Controller.
- Drag a Push Segue from the Role Table View Cell to the new table view and rename the Segue Identifier to Person Role Segue.
- Rename the new Table View Controller Navigation Item Title to Person’s Role.
- Set the Reuse Identifier of the Person’s Role Prototype Cells to Person Role Cell.
- Set the Custom Class of the Person’s Role Table View Controller to PersonRoleTVC.
The storyboard should look a little like this:

Use Assistant Editor to Contol-drag a new property from the Role Table View Cell (from Person Detail table) to the PersonDetailTVC.h file. Call the new property personRoleTableViewCell. This should create a new connected property for you automatically in PersonDetailTVC.h as follows:
@property (strong, nonatomic) IBOutlet UITableViewCell *personRoleTableViewCell; |
PersonDetailTVC.h also needs to know what a Role is so it can store the selected role for the save. To get that info back from PersonRoleTVC it needs to be its delegate too. Edit/add to PersonDetailTVC.h as follows:
#import "Role.h"
#import "PersonRoleTVC.h"
@interface PersonDetailTVC : UITableViewController <PersonRoleTVCDelegate>
@property (strong, nonatomic) Role *selectedRole; |
Of course You will now have to add a few things to PersonDetailTVC.m like this:
@synthesize selectedRole; |
…and this (remove existing save method):
- (IBAction)save:(id)sender
{
NSLog(@"Telling the PersonDetailTVC Delegate that Save was tapped on the PersonDetailTVC");
self.person.firstname = self.personFirstnameTextField.text; // Set Firstname
self.person.surname = self.personSurnameTextField.text; // Set Surname
[self.person setInRole:self.selectedRole]; // Set Relationship!!!
[self.managedObjectContext save:nil]; // write to database
[self.delegate theSaveButtonOnThePersonDetailTVCWasTapped:self];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender // !
{
if ([segue.identifier isEqualToString:@"Person Role Segue"])
{
NSLog(@"Setting PersonDetailTVC as a delegate of PersonRoleTVC");
PersonRoleTVC *personRoleTVC = segue.destinationViewController;
personRoleTVC.delegate = self;
personRoleTVC.managedObjectContext = self.managedObjectContext;
}
else {
NSLog(@"Unidentified Segue Attempted!");
}
}
- (void)roleWasSelectedOnPersonRoleTVC:(PersonRoleTVC *)controller
{
self.personRoleTableViewCell.textLabel.text = controller.selectedRole.name;
self.selectedRole = controller.selectedRole;
NSLog(@"PersonDetailTVC reports that the %@ role was selected on the PersonRoleTVC", controller.selectedRole.name);
[controller.navigationController popViewControllerAnimated:YES];
} |
The very last thing you have to do is add the following to the cellForRowAtIndexPath method of the PersonsTVC.m file:
cell.detailTextLabel.text = person.inRole.name; |
Here’s the final result:

That’s it! Stay tuned for Part 5 where I’ll discuss pre-loading default data.
Here’s the source code so far
If you liked this tutorial or found something wrong with it please let me know!
If you want to support my work and have an iPad please consider purchasing iSoccer *wink*
-Tim
Go to Part 5 or the Tutorials Index