Feature-complete Core Data example app

We build upon last week’s “CD Intro” example app, as we learn more about Core Data. Today, we move beyond read-only, and look at adding and editing. We will also use multiple entities, and multiple navigation levels and paths.

.

Full-featured Core Data navigation-based application

As you know, the window- and navigation-based application templates both offer you the choice to use Core Data. Which one should you start with?

Choice 1: Start with a window-based application template. That way, we won’t have to un-do some of the functionality in the navigation-based application template. However, we’ll have to build in the navigation functionality.

Choice 2: Start with a navigation-based template. This template is built for table view navigation, and works out-of-the-box. We will have to un-do some of the functionality.

The choice is up to you. For most projects, we’ll use the navigation-based application template.

Today’s application is based on the Computer Studies curriculum plan app from last week. We’ll add data editing functionality. Here are some screen captures:

Programs list, view, and edit: Home screen, showing a list of Computer Studies programs. Then, the blue disclosure button is tapped, to show more detail about the BSD program. Finally, the Edit button is tapped to show a screen that enables data editing.

Programs list, add new program: Home screen, tap Edit, tap + (the plus sign); ready to add a new program.

Tap a program, view course list, view and edit: Home screen, tap a program to view the course list, tap a course to see its details, tap Edit to get ready to edit the course details.

Delete a course: Home screen, tap a program to view the course list. Tap Edit to see the ready-to-delete buttons, tap a button.

Add a course: Home screen, tap a program to view the course list. Tap Edit. Then, tap + (the plus sign); ready to add a new course.

The full-featured complete sample application, called “CD Complete”, is in the downloads area.

Note – this app may get updated and re-posted during study week, to be compatible with iOS 4 changes in coding practices.

.

App design principles

The purpose of this app is to provide you with a template that you can use to create a navigation-based application that uses Core Data. You want to be able to do this reliably and predictably.

There are several “new things” in this app:

Core Data (obviously)

Table view that uses the “grouped” appearance

Table view that uses multiple sections, with a title for each section

Table view that enables 1) drill-down navigation from a row, and 2) detail discovery about a row

A navigation pattern that’s consistent and full-featured

A standard “detail” view that can be used for both “new” and “existing” items

Navigation bar buttons are used to support add/edit operations

Let’s look at the app design principles.

.

A table view must provide rich functionality

In this app, our table views must do multiple things, including:

Display a list of items

Allow the user to delete an item

Allow the user to add an item

Enable the user to drill down to the next level by tapping on an item

Enable the user to view detail about an item by tapping on its detail disclosure button

Allow the user to edit the details of an item

Each of these functions becomes a check-box item on your “coding tasks list”. You should be able to perform each coding task in a predictable way, so that the function is added correctly, and works reliably.

.

Configure the view controller with the data and behaviour it needs

From our previous work with navigation-based applications, we have learned that we configure the view controller with the data and behaviour it needs. We continue this pattern.

Incidentally… is there another way to do this? Yes. Create a custom initializer, which takes the data as an additional argument.

In a navigation-based application that uses Core Data, there are a few additional things to remember:

If you drill down from one table view to another table view (i.e. one table view pushes another on to the navigation stack), then you need to pass along the selected managed object.

Alternatively, if you drill down from one table view to a standard view controller (that shows details about the tapped item on its view), then you need to pass along the selected managed object.

Create a new fetched results controller for each table view in your navigation scheme.

Configure a fetched results controller delegate (typically the same subclass that contains the code that creates the fetched results controller; typically the table view controller subclass). It will enable your app to reliably respond to changes in the managed object context.

.

Design principles diagram

Study the diagram below. It shows the design of the application:

“Programs” is a table view controller:

MOC – the app delegate passes in the managed object context

FRC – the programs subclass creates a new fetched results controller, focused on the “Program” entity

If the user taps the detail disclosure button, they want detail about the item. The managed object – the SO (selected object) – that backs the tapped row is passed to the “View” standard view controller, where it is known as the RO (referring object).

The user can get to the “Add/Edit” view controller by using two different paths:

From the “View” details (above), they tap Edit

From the programs list, they tap Edit, then + to add a new program

“View” is a standard view controller:

RO – referring object – passed in from the Programs table view controller, where it is known as SO

“Add/Edit” is a standard view controller:

RO – referring object – passed in from “View”, where it is also known as RO

RO – referring object – passed in from “Programs”, where it is known as NMO (new managed object)

“Courses” is a table view controller:

FRC – the courses subclass creates a new fetched results controller, focused on the “Subject” entity

RO (referring object) – the Programs table view controller passes on the managed object that backs the tapped row (where it is known as SO)

.

How do we create a new course, which is related to a program?

When you create your managed object model, we recommend that your relationships are defined both ways. With our model here, the Program entity has a “to-many” relationship with Subject. We also configure subject with the inverse to this relationship, so that it has a “to-one” relationship with Program.

When you do this, setting one of the relations causes the inverse relation to be configured with no additional work by you.

This means that we can, for example, create a new Subject managed object, and set its relationship property to the Program managed object that it is related to.

.

Navigation pathways from the root to the courses add/edit view controller

Study the diagram below. It shows the view controllers when working with courses:

.

There are two navigation pathways that can be followed from the home screen (the programs list) to the “Courses” add/edit view controller screen:

View then edit path:

A program row is tapped.

The Programs table view controller passes on the selected Program managed object that backs the tapped row, to the CourseList table view controller.

Then, a course row is tapped.

The CourseList table view controller passes on the selected Subject managed object that backs the tapped row, to the “course view” view controller.

Finally, this object is passed on to the add/edit view controller.

Edit then + path:

As above, a program row is tapped.

The Programs table view controller passes on the selected Program managed object that backs the tapped row, to the CourseList table view controller.

Then, a new managed object is created, and its “program” relation is set to the passed-on referring object from the Programs view controller.

Finally, the new managed object is passed on to the add/edit view controller.

In the code, we have used instance names that match this design: selectedObject, and referringObject. Additionally, when adding a new course, the instance name in the CourseList table view controller is newManagedObject. After the object is passed to the add/edit view controller, it is known there as referringObject. (It was done this way, because the add/edit view controller is used for two purposes – adding new and editing existing.)

.

Navigation bar configuration

This app supports both navigation and data entry and editing. Therefore, we had to carefully think about the navigation bar configuration, so that it supported these functions smoothly.

For example, some examples “out there” (e.g. Apple’s Core Data Books) are inconsistent about the use of the navigation bar. Sometimes, in navigation-enabled contexts, the back button is replaced by an edit, done, or + button. In other examples, you have to click a button on the right side to edit in one context, but elsewhere in the app, you have to click a button on the left side to edit in another context. We wanted to avoid these problems here.

Therefore, we kept the edit button on the right side of the navigation bar. The only context in which it gets replaced is during the editing operation, where the button changes to “Done” (which makes sense in this context, as it is a toggle between Edit and Done). During the editing operation, the left button is “Cancel” (which also makes sense, because you can’t navigate while editing).

The left button was reserved for the back button during navigation operations. During editing operations, it is replaced by a + button (to add a new item), or a Cancel button (during editing).

The most important thing here is that, with the exception of the root view controller, the buttons get created and configured in the subclass that creates the next-level view controller instance. They’re attached to the view controller, and are available after the view controller gets pushed along. (We could have configured the root view controller navigation bar button(s) in the app delegate, but we decided to leave the template code alone.)

.

Building the application

Now, it’s time to build the application. We suggest that you re-create your own application, and use the provided sample app as a reference. All subclasses have been // liberally commented. Follow along below. Test your work (by running it) as you create your app.

.

Create the application

Use the navigation-based application template, with Core Data.

The project starts with an app delegate and a root view controller. It also has a window nib, and a root view controller nib. You can run the application, and it will work. After you do this, delete it from your iPhone Simulator, so that you don’t get error messages after you change the data model in the next step.

The app delegate doesn’t get any changes at this point in time – it’s good to go.

.

Modify the data model

The existing entity is useless to us, so we’ll just delete it.

The first entity that we’ll create is the Program entity, representing one of the School of Computer Studies degree, diploma, or certificate programs.

Please note that you cannot use “name”, “self”, or “description” as the name of the attribute. The Core Data Programming Guide says that “a property name cannot be the same as any no-parameter method name of NSObject or NSManagedObject [or NSPropertyDescription]”.

The next entity that we’ll create is the Subject entity, representing one of the subjects (courses) in a program.

Program and Subject will have a bi-directional relationship. Note that in real life, the Subject-to-Program relationship could be “to-many” (i.e. a course like EAC150 could be part of many programs). However, for this application, it will be a “to-one” relationship to keep it simple and to clarify the concept. For complete details about the optional/required, type, and default values of the properties, see the sample app.

.

Fix the root view controller

The template code has an implementation of insertNewObject. We need to change its behaviour, so that it creates a new managed object, and passes it on to a view controller that’s dedicated to data entry. (We’ve also decided that this data entry view controller can fulfill the data editing role, discussed later.)

Here are some of the highlights of the work done now in the root view controller, in the Application lifecycle area:

viewDidLoad hasn’t changed much. We left the button-initializing code there.

viewWillAppear gets implemented, simply to reload the table view. (We may not even need this here, because the fetched results controller delegate method will take care of reloading after any data gets changed.)

We implement an override to setEditing:animated. Normally, if you place an editButtonItem on the navigation bar, the default implementation of this method takes care of toggling the button state between Edit and Done. However, we want custom behaviour – we also want a + button to appear if we’re in edit mode. Therefore, we have to override the method.

Note that all overrides should call super (with its normal signature) before doing other operations.

Here are some of the highlights of the work done now in the root view controller, in the Core Data operations area:

The fetched results controller gets created here. We have made a minor change to the template’s version – we added another sort descriptor. This piece of goodness will enable us to use the table view “grouped” view. We will sort first by “credential” – degree, diploma, certificate. Then, we’ll sort by program “code” – CPA, CPD, etc. When displayed in a grouped table view, we’ll get the nice separations you see in the screen captures.

The insertNewObject template method has been changed. It now follows the standard “navigation-based application” pattern, where we create an instance of a next-level view controller, configure it, and push it onto the navigation stack.

One of its configuration tasks is to set navigation bar buttons. We do it here, rather than in the next-level view controller’s viewDidLoad method.

We have also implemented a fetched results controller delegate method. Of the four methods that we could implement, we have decided on the simplest – controllerDidChangeContent. This is ideally suited for situations like ours – a data source change happens, we need the table reloaded, and we don’t allow user-initiated row reordering.

Here are some of the highlights of the work done now in the root view controller, in the Table view methods area:

In past navigation-based examples, you know that these four methods get implemented:

Data source – tableView:numberOfSectionsInTableView

Data source – tableView:numberOfRowsInSection:section

Delegate – tableView:cellForRowAtIndexPath:indexPath

Delegate – tableView:didSelectRowAtIndexPath:indexPath

Most of these are similar to past examples, except that Core Data makes some things a bit easier (even though some of the concepts require a bit more abstract thought).

In tableView:cellForRowAtIndexPath, notice the additional cell configuration. We can use cell.detailTextLabel.text because the cell was initialized with the UITableViewCellStyleSubtitle style.

Note also the detail disclosure button – to use its functionality, we must implement the method tableView:accessoryButtonTappedForRowWithIndexPath:indexPath, discussed soon. A “detail disclosure” button works this way:

Tapping the detail disclosure button enables navigation to a view controller that typically displays more detail about the object that backs the row

Tapping elsewhere on the row enables navigation to the next level of view controller, where typically (in this situation) you are drilling down to show another table view

In tableView:didSelectRowAtIndexPath:indexPath, the typical pattern is followed – create, configure, push. Remember the guideline above: If you are navigating to a next-level table view controller, pass along the managed object, which includes the managed object context as a property. The context is needed there by the fetched results controller creator method.

The tableView:titleForHeaderInSection:section method is called (by the view loading nib code) once per section, and it asks the fetched results controller for the text to be displayed as a section group header.

In tableView:accessoryButtonTappedForRowWithIndexPath:indexPath, we do a typical next-level navigation operation. Here we are navigating to a detail view, and not a table view controller.

In tableView:commitEditingStyle:editingStyle:forRowAtIndexPath:indexPath, we handle the request to delete a row. You may note that it doesn’t have any code to reload the table view, which is necessary after a row gets deleted. How does the table view get reloaded? The fetched results controller listens for changes in the managed object context. If there’s a change, it notifies the delegate. In our case, it is this class. The controllerDidChangeContent method runs (which reloads the table view).

Here’s a layout of the methods in the root view controller:

.

Add a view controller that will enable you to add a new program

Above, we saw how the existing template logic enables us to quickly generate new objects. Here, we will create a view controller that enables the user to enter all details for a new Program. Before continuing, notice that the managed object model has reasonable initial values for each attribute. This fact may make getting started on your code a bit easier.

Our “version 1” view controller will be called “ProgramEdit”. We’ll use it now to add new programs, and later to enable editing of an existing program.

Reminder: This view controller will get pushed by the parent when a user taps Edit then + (to add a new program).

The organization of this subclass’ methods is as follows. Its organization is simpler than that of a UITableViewController subclass.

.

viewDidLoad is important here. While today’s sample app is working fully, your initial version won’t have to determine whether we’re adding a new program, or editing an existing one. So, until later, we need to focus only on the cancel and save methods.

The cancel method will simply 1) delete the managed object that was added to the context by the parent view controller, and 2) navigate back to the parent by “popping”.

The save method takes the user-entered data and places the values into the new managed object. Then, the context is saved.

credentialSelected: is a simple helper method that configures the interface to ensure correct data entry. For example, a “certificate” credential has only 2 semesters, whereas a “diploma” credential can have 4 or 6 semesters.

.

Add a view controller that will enable you to view the details of an existing program

Now, we’re interested in a standard view controller that will use mostly UILabel controls to display the details of an existing program. This view controller is pushed by the parent in the tableView:accessoryButtonTappedForRowWithIndexPath:indexPath method.

viewWillAppear does most of the work, by displaying the program’s details in UILabel controls. We want our code here, rather than in viewDidLoad, to avoid caching problems (viewDidLoad may not run if there’s no memory pressure).

The only other bit of functionality will be to add a method that will handle the navigation bar’s Edit button tap. We’ll want to do a standard create-configure-push navigation to the ProgramEdit view controller we just created above. When we do, we’ll configure the new view controller instance with the managed object we are focused on.

.

Go back and modify the ProgramEdit view controller to support editing an existing object

We just configured the above view controller to push ProgramEdit. So, let’s edit ProgramEdit, in viewDidLoad, to determine whether the managed object passed to it is new or existing. If it’s existing, then we’ll configure the user interface with its attribute values.

.

Memory management note

You may note that the managed object of interest to us typically gets created as follows:

selectedObject then gets assigned to the next-level view controller before pushing. Notice that we do NOT release selectedObject. This is because we don’t own it – don’t release what you don’t own (via alloc init).

.

Add the components that will handle courses

At this point, the app’s components that deal with programs have been configured, and should be working well. Now it’s time to add the components that deal with courses.

Well, the good news is that we repeat most of the above, with a few differences:

We do not have a next-level table view controller to go to, so we don’t have to worry about that

Therefore, we don’t have to use a detail disclosure button, because a row tap will cause the next level view controller to be pushed

Otherwise, the principles are the same. Study the diagram below:

“Courses” is a table view controller, which appears when a user taps on a “Programs” row:

RO – referring object, which is passed in from the parent (i.e. the “Programs” table view controller); this is the managed object that backs the tapped row in the parent

SO – selected object in THIS table view controller – see below for managed object usage

If the user taps anywhere on a row, they want detail about the item. The managed object that backs the tapped row is passed to the “View” standard view controller.

The user can get to the “Add/Edit” view controller by using two different paths:

From the “View” details (above), they tap Edit

From the programs list, they tap Edit, then + to add a new program

“View” is a standard view controller:

RO – (incoming) referring object – passed in from the course list table view controller, where it is known as SO

RO – (outgoing) referring object, which is passed along to the add/edit view controller

“Add/Edit” is a standard view controller:

RO – referring object, which is passed in from either “Courses” or “View”

.

Fetched results controller – info about a predicate

In the fetched results controller, note the predicate format:

@”attributeName == %@”

This predicate checks whether the value of the key attributeName is the same as the value of the object %@ that is supplied at runtime as an argument to predicateWithFormat:. Note that %@ can be a placeholder for any object whose description is valid in the predicate, such as an instance of NSDate, NSNumber, NSDecimalNumber, orNSString.

The Predicates Programming Guide has a “Format String Summary” in the “Creating Predicates” section, starting at the bottom of page 12 (2010-06-14 PDF version).

Any object? How about the selected “Program” managed object? Yes. Check the CourseList.m for details.

.

Summary

This has been a lengthy process. However, we hope that you see how it results in a set of repeatable tasks, which enable you to approach the design and coding of YOUR application in a structured and predictable way.

If you have any corrections, or if you find any bugs, let me know (via the comments), and I’ll take care of them.
.