Monday, July 18, 2011

Simple Core Data Tutorial

This is our long overdue Core Data Tutorial. We are going to create an app similar to our SQLite3 tutorial. Where we store the NBA player's name and nickname (well in SQLite3 tutorial it was name and team) in a Core Data storage.

Quick guide about Core Data- it provides an abstraction from our persistent store, which for most of the time is still going to be SQLite3. But instead of manually writing SQL statements and accessing SQLite3's C API, we are going to do our saving and fetching all in objective C.

In Core Data, a table is an entity description and a table field is an attribute of that entity. In runtime an entity becomes an NSManagedObject or a subclass of it. It sounds complicated I know, but its not as hard as you think.

We also need an NSManagedObjectContext which we get from our app delegate. We get this free when we check the use core data storage checkbox when we create a new project. Now the managed object context is a scratch pad that monitors changes to our object and it is the one in charge of executing saves and retrievals. For instance, it makes sure that we don't have an object that is only partially saved.

So lets get started!

This is how our final app will look like:


The first view lets you create a player name and player nickname. After you press save it pushes to a table view that lists all the players that were saved in our core data.

So open xcode > create new project >window-based application with core data sa storage checked > name it players_SG.

First lets create our data model in Core Data. Right under your resources folder, you will see players_SG.xcdatamodeld. Click that and you will see players_SG.xcdatamodel. This is what we need. Double click to open it in our model editor.


Create an entity by clicking the plus (+) button near area (1). Then change the name (3) to Player. We'd like to capitalize the first letter of the entity name and small caps for our attribute name later. Notice that the class is NSManagedObject, that is because at run time, our data becomes an NSManagedObject. If you wish we can also subclass NSManagedObject. But we won't be doing that in this simple tutorial.

Then create an attribute by clicking plus (+) at area (2) select add attribute. At area (3):

Make the attribute Name: name.
Leave the optional checked. This means that the value of name can be nil. Transient means attributes are used to store non standard objects for which there is no pre-defined type. Now, change the type to String. At runtime this becomes NSString. Well, obviously right. I told you its not that difficult. You can also specify min max length, reg ex and default value. But we don't want to limit our user from creating the name of a certain player so lets keep it a simple string.

Next make another attribute named: nickname. Also optional and String. Now save our data. 

Lets move to code. Go to resources folder, right click add> new file> UIViewController subclass, with nib. Save as addViewController.

Edit addPlayerViewController.h.
We add two outlets for our nib. And a method to save our data to core data.

Now at the implementation file. Synthesize our outlets.

After @implementation addPlayerViewController

type:
synthesize nameTextField, nicknameTextField;

at the dealloc 
type:
[nameTextField release];
[nicknameTextField release];

Press command-B to build the project without running it. We need it to connect our outlets to our UI in Interface Builder.

Double click addPlayerViewController.xib. Edit it at IB to look like this.

Connect your textfields to the approprite outlets. Control-click from file's owner to the top textfield, select name. Do the same for the second textfield, select nickname this time. Select the top textfield and press command 1 to toggle the attributes inspector. At placeholder: type name. So the label "name" appears as a light gray label inside our textfield. It saves us having to drag a label on top of our textfield to mark it as the "name" textfield. Plus, I think its better UI. Now, do the same for our next textfield, this time change the placeholder text to "nickname".

Control -s to save the nib, now go back to xcode.

In our viewDidLoad, we just create a save button in our navigation controller on the upper right corner of the screen. This button calls the method savePlayer when pushed. So we implemented in our -(void)savePlayer.

First we create na instance of ListViewController. Which is the table view for the players. Sorry for including this here, we will create it a little later don't worry.

For our core data to work, we first need to get our managed object context from our appdelegate's property.

Then we create an NSManagedObjectContext using the NSEntityDescription insertNewObjectForEntityForName. We name it Player because as you remember in our data modeler, our entity's name is Player. Next we populate our entity by calling setValue: forKey: in our NSManagedObject. In a way this is quite similar to NSDictionary right? Here we just set the value of our name attribute to whatever the user types in the nameTextField. And our player's nickname to whatever the user types in our nickname textfield. Easy right?

Now to save it we need to declare an NSError object just in case we need it when the save is unsuccessful. As you remember, I said it is the NSManagedObjectContext's job to save. So the call [context save:&error]; saves our data to our model. We can also create an if statement to log errors in case this fails.

The next line just pushes the table view with all the saved records. So lets do that now...

Go to classes folder, right click select add new file>UIViewController> check subclass of UITableView Controller, no nib for this. Name it ListOfPlayersViewController.

Edit the interface file:

First thing we do is to make our class conform to the NSFetchedResultsControllerDelegate. This makes it easier for us to display our managed objects from core data to a table view. It also gives us a lot of functionality for free like sections, and gives us the ability to edit and delete entries in core data by deleting rows in our table view. Cool huh? 

In our instance variable, we declare an NSFetchedResultsController object and our NSManagedObjectContext. Then we make our NSFetchedResultsController as a property so that we can have getters and setters for it.

Now head on to our impelementation file.


In our viewDidLoad, we just created an edit button on the right side of our navigation controller. Also typed in our title.

Now the next method is the getter method for our fetchedResultsController, as I said we need this so our table view can conviniently display our core data entries.

First we check if we already created our instance of fetchedResultsController. We lazily instantiate if not.  

To create our fetchedRequestController we first need to get to our context, again its from our app delegate. Then we create an NSFetchRequest. This is like a SQL statement for requesting objects from our tables. We create a fetch request by passing it an NSEntityDescription, like what table do we want our DB to query, if this were SQL. It also lets us narrow a search by giving us a chance to pass it an NSPredicate. Though we won't do that here, because we want all our data presented. Also, it gives us a fetch batch size, in case you have gazillions of data in your persistence store, your iphone won't fetch all of them at the same time, only the batch size.

Next we pass an array of sort descriptor. Its an array because it will sort first by using the first sort descriptor and moves on the next. Kinda like sorting the lastnames first before sorting the firstname.

Then after we have our fetch request object. We can create an NSFetchedResultsController and then pass our fetch request object to it. The section name key path is just which attribute or relationship is used to make the sections in our table view.

Then we declare the self as the NSFetchedResultsController delegate. That means we have to implement its methods.

Then we let it performFetch. Which is the actual command to fetch our records from core data.


These methods are responsible for coordinating the core data entries with the table view entries. So if you for example delete a cell from table view, it also deletes the same one from core data. 

For more details on this methods, head on over to apple's documentations, where I literally copy pasted these methods. :)

Lets go hook our table view methods to our NSFetchedResultsController:

Again I grabbed this from apple's documentation. It just makes sure to display the correct number of sections, correct number of rows in the section, correct title of the section's header,and  the correct cell to display in a given row. It also has the method for editing the table view, in this case the deletion of a cell.

Pay attention to cellForRowAtIndexPath: method. Here, we get our NSManagedObject Player from our NSFetchedResultsController. Then we extract our name and nickname from the player using the valueForKey: method. Then we display it to our table view.

Now, our table view is synced with our core data, using the NSFetchedResultsController. It also gives us free functionality like, deleting rows and displaying sections in our table view. Not too bad eh?

Build and run! Enjoy!