In this tutorial ( iOS Core Data Tutorial ), we will learn how to save and fetch data in iPhone Apps and also manage relationships between them. Will will create a phone book core data project with complete source code provided in the end.
Final Project – iOS Core Data Tutorial
What is Core Data in iOS
Core Data is a powerful unified framework Provided by Apple for persisting data in the applications. It is cheap in terms of processes usage, and relationship between data can also be maintained easily.
Tutorial Overview
In this Tutorial , We will learn basic implementation of Core data
- How to Add core data files to an existing Project.
- Manage NSManageObject Classes.
- Use Relationship among NSManageObjects.
- Fetching data.
- Showing Data to the user.
Initial Setup
This tutorial aims at core data implementation for beginners, so we have already set up the project with the views we needed and the controllers hierarchy.
For understanding the setup, you need to have basic knowledge of Table view controller and adding new project (If you don’t know this , you can read our previous tutorials from here )
Short note on already set up project (Project can be downloaded from link below )
Initial Project – download Link – iOS Core Data Tutorial
- MainViewController – Initial View controller : which contains a form to add new entry to your phone book and a button to show already added entry.
- PhoneBookTableViewController : This will be used to show already added phone book entries.
You can download the initial project and run it and you will see the form to add entry and tapping show entry button will take you to table view controller (its empty as we have not added any data to it yet)
Now we will move on to adding core data functionality.
Add Missing Framework
- Tap on project file , go to Targets.
- Open the summary tab.
- Click on ‘+’ inside Linked Frameworks and Libraries (shown as 1 in the screen).
- Add CoreData.Framework.
- Move this framework under Framework folder inside project Navigator Tab.
Adding Missing Files
- From Xcode,click on the File Tab and go to new file (or click Cmd + N).
- Select Core data from the iPhone OS group.
- Select Data Model and click Next.
- Give the file a name (the project name is a good choice) and click Create.
Now we have an Empty file with name “PhoneBook.xcdatamodeld”
Adding Missing Objects
1. Open PhoneBook-Prefix.pch (Your project pch file ) and add following code to it
1
|
#import <CoreData/CoreData.h>
|
This is added to make project compile core data framework and make it available for all files in the project as it is being added to .pch file (pre-compiled header)
2. Open AppDelegate.h and add following code (objects)
1
2
3
|
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator ;
|
As you can see, We have created instance of NSManagedObjectModel , NSManagedObjectContext , NSPersistentStoreCoordinator respectively , that will be used to deal with data base .
3. Now Open AppDelegate.m and add following code
1
2
3
4
5
6
|
@implementation ApDelegate
@synthesize managedObjectContext = _managedObjectContext;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
|
4. To the same file (AppDelegate.m) , add Following Methods also :
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
45
|
// 1
- (NSManagedObjectContext *) managedObjectContext {
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return _managedObjectContext;
}
//2
- (NSManagedObjectModel *)managedObjectModel {
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
_managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
return _managedObjectModel;
}
//3
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory]
stringByAppendingPathComponent: @"PhoneBook.sqlite"]];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
if(![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:storeUrl options:nil error:&error]) {
/*Error for store creation should be handled in here*/
}
return _persistentStoreCoordinator;
}
- (NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
|
1) Method will be returning “managedObjectContext” object associated with out data base file (Sqlite ).
2) Method will be returning “managedObjectModel” object associated with out data base file (Sqlite ).
3) Method will be returning “persistentStoreCoordinator” object associated with out data base file (Sqlite ).
Some useful Definitions
Persistent Store Coordinator:
As the name suggest it is a coordinator , that coordinates between manage object context and low level file saved in our data base (Sqlite file) , you do not have to think much about it as this stage. We are not gonna use it directly, it will only be used while setting NSManageObjectContext . Linking it to our Persistent store coordinator which in turn will be linked with our Sqlite file (PhoneBook.sqlite for our case) .
Managed object context
You can think of a managed object context as an intelligent scratch pad. When you fetch objects from a persistent store, you bring temporary copies onto the scratch pad . You can then modify those objects however you like. Unless you actually save those changes, however, the persistent store remains unaltered.
This will be most important as we will be using it to make queries on our data (fetching , updating , deleting and adding data).
NSManageObject Model
A managed object model is an instance of the NSManagedObjectModel class. It describes a schema (contains definitions) for objects (also called entities )—that you use in your application.
Properties (Attributes of entity )
An entity’s properties are its attributes and relationships. Amongst other features, each property has a name and a type. A property name cannot be the same as any no-parameter method name of NSObject or NSManagedObject—for example, you cannot give a property the name “description” (see NSPropertyDescription).
Generating Our Model
- Tap on “PhoneBook.xcdatamodeld”
- You will see a screen like this
1) Tap this Button to add New Entity
2) Tap this to Add new Property (Attribute) to selected Entity.
3) Tap this to Add new Relationship to selected Entity.
4) This Column will tell you the Attribute name.
5) This Column will tell you the Attribute Type.
Using Above info , we have created our Entity named “Record” with 3 Attributes as shown in screen :
1) This Text (Record) will be used as class name when you use auto generate. (just remember it for now, Auto generate process will be explained below).
Auto Generating NSManageObject class
Go to File tab and tap on new file ( or Cmd + N) when the ”PhoneBook.xcdatamodeld” is still open
- Select Core data from the iPhone OS group.
- Select NSManageObjectSubclass and click Next
- Record.h and Record.m will be created automatically with all the properties defined.
Now run the project , it will run as before. If there is any warning or error.
You may have missed any step check all the steps again. But still there is no data as we have not yet saved any Records to our Data base.
Saving Records to Database
So far , we have added core data files , framework and generating our model file .
Now, We are going to the most important and useful part of the tutorial,
We are going to add Our data to Data base.
Open MainViewController.m
Add following property (write below code above method definitions)
1
|
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
|
Import Required header Files
1
2
|
#import "Record.h"
#import "AppDelegate.h"
|
Add following code to viewDidLoad Method
1
2
3
4
|
//1
AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
//2
self.managedObjectContext = appDelegate.managedObjectContext;
|
1) Make an instance of AppDelegate .
2) Referencing manageObjectContext : manageObjectContext of the Controller points to AppDelegate’s manageObjectContext object.
Change addPhoneBookEntry method to following:
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
|
- (IBAction)addPhoneBookEntry:(id)sender
{
// Add Entry to PhoneBook Data base and reset all fields
// 1
Record * newEntry = [NSEntityDescription insertNewObjectForEntityForName:@"Record"
inManagedObjectContext:self.managedObjectContext];
// 2
newEntry.firstName = self.firstNameTextfield.text;
newEntry.lastName = self.lastNameTextfield.text;
newEntry.city = self.cityTextfield.text;
// 3
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
}
// 4
self.firstNameTextfield.text = @"";
self.lastNameTextfield.text = @"";
self.cityTextfield.text = @"";
self.phoneNumber1.text = @"";
self.phoneNumber2.text = @"";
// 5
[self.view endEditing:YES];
}
|
1) Creates, configures, and returns an instance of the Record class .
2) We add Values to different Attributes of Record class object.
3) Entity value is saved to data base (Without this our data won’t persist in the memory ). As i already told you its like scratch pad unless you save the work. it will be lost when scratch pad is erased.
4) Removing All Text field in Texfields.
5) Dismissing Keyboard.
Screen showing an Example Record
Show Records In TableView
1. Open AppDelegate.h and add Following Methods
1
|
-(NSArray*)getAllPhoneBookRecords;
|
2 . Now Open AppDelegate.m and add implementation of the Method added.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
-(NSArray*)getAllPhoneBookRecords
{
// initializing NSFetchRequest
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
//Setting Entity to be Queried
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Record"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSError* error;
// Query on managedObjectContext With Generated fetchRequest
NSArray *fetchedRecords = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
// Returning Fetched Records
return fetchedRecords;
}
|
3. Now Open PhoneBookTableViewController.m
Firstly Import some header Files
1
2
|
#import "AppDelegate.h"
#import "Record.h"
|
Add a Property of type NSArray to save fetched Objects (Records)
1
|
@property (nonatomic,strong)NSArray* fetchedRecordsArray;
|
Modify viewDidLoad Method to look like following
1
2
3
4
5
6
7
8
9
|
- (void)viewDidLoad
{
[super viewDidLoad];
AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
// Fetching Records and saving it in "fetchedRecordsArray" object
self.fetchedRecordsArray = [appDelegate getAllPhoneBookRecords];
[self.tableView reloadData];
}
|
Now , we will use this array as a data source to update our table view
Modify UITableViewData source method to incorporate our fetchedRecordsArray to populate the table cells
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [self.fetchedRecordsArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
staticNSString *CellIdentifier = @"PhoneBookCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Record * record = [self.fetchedRecordsArray objectAtIndex:indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:@"%@ %@, %@ ",record.firstName,record.lastName,record.city];
return cell;
}
|
Now Run the program again, and tap on show PhoneBook. You will see Records like this
But wait a minute, what about the phone numbers ?
Oh Ya! We have not added relationships to show Phone Numbers Yet.
Adding Relationships
Relationship helps to maintain Records in neat and clean way .
Creating a relationship in a managed object model is straightforward, but there are a number of aspects of a relationship that you need to specify properly. The most immediately obvious features are the relationship’s name, the destination entity, and the cardinality (is it a to-one relationship, or a to-many relationship).
Updating Model Files
1. Add Entity to save phone number :
Add New Entity named as “PhoneNumber” with one Attribute named “number” of type String.
2. Deciding Type of Relationship:
Now we have to make a relationship between Record and Phone Number . Which type of relationship to be used will be decided by the data.
We have two phone number for every record so lets use one to many relationship.
3. Making Relationship
Go to Record and tap on plus to add relationship with name “phoneNumbers” , add PhoneNumber Entity as Destination
Similarly go to PhoneNumber , make a relationship which is to be used as inverse relationship.
Name this Relation as record with Record as its destination.
Following Screen Explains you Various things You need to understand :
1) This Column shows the various Relationships name.
Come back to Record Entity , tap on relation .
“M” Shown before name of relationship means it is a one to many relationship
“O” means it is one to one relationship (we don’t have it here)
2) This Column shows the Destination class for that Relation.
3) This Column shows the Inverse Relationship name.
4) This Shows the Checkbox which is to be checked to make relationships one to many .
5) Show Various Entities in the Model with the open one is shown as Highlighted.
Why to add Inverse Relationships :
Here are the Lines from Apple Documentation ..
You should typically model relationships in both directions, and specify the inverse relationships appropriately. Core Data uses this information to ensure the consistency of the object graph if a change is made. (For Example if a object is deleted)
Now we have added the Relationship By its time to change it to one to many :
Come back to Record Entity , tap on relation .
From the right menu , tap on Data Model inspector and define relationship as one to many as shown in above screen :
After Completing this task PhoneNumber Entity will look like this
Now Run the project and Try to add Record with Phone Numbers ?
Project Crashes!!
Important Note :
AutoGenerating Classes Again
Delete already generating Record.h and Record.m files.
Open ”PhoneBook.xcdatamodeld” and select both Entities .
Go to File tab and tap on new file ( or Cmd + N) when the ”PhoneBook.xcdatamodeld” is still open
- Select Core data from the iPhone OS group
- Select NSManageObjectSubclass and click Next
- Both Classes will be created with all the attributes and relationship as defined.
So , We delete the app from simulator ,clean the project and build it again.
And Hurray ! That works
But still no Phone number info. Because we have created the relationship but has not used it to save our data.
Saving Data Using Relationships
1 . Open MainViewController.m
Firstly Import Header Files
1
|
#import "PhoneNumber.h"
|
Modify addPhoneBookEntry Method to look like Following
points 6,7,8 has been added to save data in relationship .
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
|
- (IBAction)addPhoneBookEntry:(id)sender
{
// Add Entry to PhoneBook Data base and reset all fields
// 1
Record * newEntry = [NSEntityDescription insertNewObjectForEntityForName:@"Record"
inManagedObjectContext:self.managedObjectContext];
// 2
newEntry.firstName = self.firstNameTextfield.text;
newEntry.lastName = self.lastNameTextfield.text;
newEntry.city = self.cityTextfield.text;
// 6
PhoneNumber * phoneNumber1 = [NSEntityDescription insertNewObjectForEntityForName:@"PhoneNumber"
inManagedObjectContext:self.managedObjectContext];
phoneNumber1.number = self.phoneNumber1.text;
// 7
PhoneNumber * phoneNumber2 = [NSEntityDescription insertNewObjectForEntityForName:@"PhoneNumber"
inManagedObjectContext:self.managedObjectContext];
phoneNumber2.number = self.phoneNumber2.text;
// 8
newEntry.phoneNumbers = [NSSet setWithObjects:phoneNumber1 ,phoneNumber2, nil];
// 3
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
}
// 4
self.firstNameTextfield.text = @"";
self.lastNameTextfield.text = @"";
self.cityTextfield.text = @"";
self.phoneNumber1.text = @"";
self.phoneNumber2.text = @"";
// 5
[self.view endEditing:YES];
}
|
6) Making an Entity Object For PhoneNumber Class and saving phoneNumber1 in it.
7) Similarly making one more object to save PhoneNumber2.
8) Adding these Objects as relationships to Record object. (There are no Array in Data base, only NSSet).
Now , Run the project and add more objects. See this Object In table view.
Still we don’t see Any Phone Number , We need to change Cell Data to show both phone numbers.
2. open PhoneBookTableViewController.m
Firstly Import Header Files
1
|
#import "PhoneNumber.h"
|
Modify cellForRowAtIndexPath Data Source Method to look like following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"PhoneBookCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Record * record = [self.fetchedRecordsArray objectAtIndex:indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:@"%@ %@, %@ ",record.firstName,record.lastName,record.city];
//1
PhoneNumber* phoneNumber1 = (PhoneNumber*)[[record.phoneNumbers allObjects] objectAtIndex:0];
PhoneNumber* phoneNumber2 = (PhoneNumber*)[[record.phoneNumbers allObjects] objectAtIndex:1];
//2
cell.detailTextLabel.text = [NSString stringWithFormat:@"Phone Numbers- %@,%@ ",phoneNumber1.number ,phoneNumber2.number];
return cell;
}
|
1) PhoneNumber object extracted from FetchedObjectsArray Records.
2) “number” Extracted from PhoneNumber Objects and shown in Cell.
Add Some More Entries with all the fields inserted and see that in PhoneBookTableViewController
BinGo !
We have Our Data in here!
We are done here with the first part of this Tutorials , In this Tutorial we have learnt how to Add Core Data in Existing Projects :
- How to add core data files.
- Making Model Entities with no Relationships.
- How to add Relationships
- How to fetchData and show them in TableView
Source Code – iOS Core Data
Download the complete source code here and if you have any questions or feedback, leave me a comment.
What Next
In the Next Part of this Tutorial, we will deal with updating (or deleting ) already existing records in our core data.