Chapter 9. Data: advanced techniques

 

This chapter covers

  • Using SQLite
  • Using the Address Book framework
  • Using Core Data

 

In the last chapter, we discussed how information can be saved and retrieved on the iPhone and iPad. Those techniques are great for simple data such as user preferences, but what happens when you want to save more complicated large amounts of data?

Saving large amounts of information to NSUserDefaults would be awkward and clunky, and serialization is too slow. The solution is to use a relational database. Apple has provided a couple of options for mass storage using relational databases. These options are SQLite and Core Data. We’ll look at both as well as the built-in Address Book framework, which isn’t related to data storage but has some complexities of its own.

SQLite is a compact version of MySQL. Even though it doesn’t offer as many field types as MySQL, it’s still a powerful method of storage. One of the greatest strengths of an SQLite database is its portability. Unlike MySQL, it doesn’t require a server in order to run. You can drop the database into your application directory and start using it to store persistent data.

Core Data is a powerful layer that sits on top of an SQLite database. It removes many of the complexities of SQL and allows you to interface with the database in a more natural way. It does this by making the database rows into real Objective-C objects (called managed objects) and lets you manipulate them without any knowledge of SQL. We’ll be discussing both methods of storage and leave it up to you to decide which works best with your project.

9.1. Using SQLite

Let’s look more closely at what SQLite is, look at how to set up and access an SQLite database, and finally explore an example that puts an SQLite database in practice.

The SDK’s built-in database is SQLite, a public domain software package. You can find more information on it at www.sqlite.org, including documentation that’s considerably more extensive than what we can include here. You need to know the SQL language to use SQLite, and we won’t cover SQL syntax here at all. In addition, you must be familiar with the SQLite API. We’ll show how to use it for some basic tasks here, but there’s a much more extensive reference online.

SQLite has what we find to be two major limitations. First, there’s no simple way to create a database. You must create the database by hand for now. Second, SQLite has no object-oriented interface. Instead, you use an API that falls back on C code, which we find less elegant and harder to use than the typical Objective-C class.

Given these limitations, we still think that using an SQL database is a better option than files for most situations, and we highly suggest that you learn enough about SQL to use it comfortably.

9.1.1. Setting up an SQLite database

Prior to using SQLite in your program, you must set up a database that contains all of your tables and the initial data you want. We’ll look at the general steps first, and then you’ll set up a database that can be used to drive a navigation menu.

Creating an Sqlite Database

Creating an SQLite database typically is done from the command line, although it can also be done entirely programmatically. We won’t cover programmatic creation of the database here, but you can find documentation on the SQLite site for doing that. The steps for creating a database from the command line are listed in table 9.1.

Table 9.1. Creating an SQLite database from the command line

Step

Description

1. Prepare your table. Figure out the design of each table in your database. Create a file for the initial data of each table (if any) that has data cells separated by pipes (|) and data rows separated by returns.
2. Create your database. Start SQLite with this command:
sqlite3 filename
Use a CREATE TABLE command to create each table.
3. Enter your initial info. Use this command to fill each table:
.import table filename
Quit SQLite.
4. Add your database to the Xcode. Inside Xcode, use the Add > Existing Files menu option to add your database to your project.

To show how all this works, you’ll put together a data file for a database-driven navigation controller. When we talked about tables in chapters 5 and 7, you created them from arrays and dictionaries. This is a fine technique when you’re creating small, stable hierarchies, but what if you want to build something larger or something that can be modified by the user? In those cases, a database is a great backend for a navigation menu.

Designing a Navigation Menu

To support a database-driven menu, we’ve designed a simple database schema. Each row in the navigation hierarchy is represented by one row in a database. Each of those rows has five elements:

  • catid—Provides a unique (and arbitrary) ID for an individual row in the menu
  • parentid—Indicates which row in the database acts as the hierarchical parent of the current row, or lists 0 if it’s a top-level row that would appear on the first page of the menu
  • title—Contains the printed text that will appear in the menu
  • entrytype—Specifies whether the row is a category (which opens a submenu) or a result (which performs some action)
  • ordering—Lists the order in which the rows should appear on an individual page of the menu

Here’s an example of what a data file might look like, with the five elements shown in the preceding order:

> cat nav.data
1|0|First|category|1
2|0|Third|category|3
3|0|Second|category|2
4|2|Submenu|category|1
5|0|Action #1|result|4
6|1|Action #1B|result|1

And here’s how you create a table for that data and import it:

> sqlite3 nav.db
SQLite version 3.6.12
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> CREATE TABLE menu (catid int(5),parentid int(5),title
     varchar(32),entrytype varchar(12), ordering int(5));
sqlite> .import nav.data menu

Note that to quit SQLite, type .quit and press Enter.

Afterward, you can add your now-complete database to Xcode using the normal procedures, a step that’s not shown here. After you’ve linked in your database the first time, you can go back and make changes to it, and the new version will always be used when you recompile your project.

You now have a ready-to-run database, but you’ll still need to prepare your Xcode to use SQLite. We’ll look at that next.

9.1.2. Accessing SQLite

You have to link in some additional resources to use SQLite, as is typical for any major new functionality. First, you need to add the framework, which you can find under /usr/lib/libsqlite3.0.dylib, rather than in the standard framework directory. Second, you must add an import of sqlite3.h.

You now have a database that’s ready to use, and you’ve included the functionality that you need to use it. The next step is to access SQLite’s functions.

9.1.3. Accessing your SQLite database

SQLite includes approximately 100 functions, about 20 object types, and a huge list of constants. We’ll cover the basics that you’ll need to access the database you’ve created. Table 9.2 shows the most critical API commands. They generally revolve around two important concepts: the database handle (which is returned by sqlite3_open and is used by everything else) and the prepared statement (which is returned by sqlite3_prepare and is used to run queries).

Table 9.2. The most important SQLite API commands

Function

Arguments

Summary

sqlite3_open filename, address of database Opens a database.
sqlite3 prepare database, SQL as UTF-8, max length to read, address of statement, address of unread results Turns an SQL statement in UTF-8 format into a pointer to a prepared statement, which can be handed to other functions.
sqlite3_step prepared statement Processes a row of results from a prepared statement, or else returns an error.
sqlite3_column_int prepared statement, column # Returns an int from the active row. Several other simple functions similarly return a specific column from the active row.
sqlite3_column_string prepared statement, column # Returns a char *, which is to say a string, from the active row. Several other simple functions similarly return a specific column from the active row.
sqlite3_finalize prepared statement Deletes a prepared statement.
sqlite3_close database Closes a database.

These functions, in order, show the usual lifecycle of an SQLite database:

  1. Open the database.
  2. Prepare statements, one at a time.
  3. Step through a statement, reading columns.
  4. Finalize the statement.
  5. Close the database.

SQLite includes two convenience functions, sqlite3_exec() and sqlite3_get_table(), that simplify these steps. But the functions are built using the core functionality just mentioned, so that’s what we’ve decided to highlight.

9.1.4. Building a navigation menu from a database

Now that you have a basic understanding of the SQLite functions, you can put together a prototype of a database-driven menu navigation system. What you’ll do here is by no means complete, but it’ll give you a great basis to build on. This example will also be one of the most complex in the book. It includes multiple classes of new objects designed to work either apart (in different programs) or together.

In this section, we’ll cover the SKDatabase class (which abstracts database connections), the SKMenu class (which abstracts navigator menu creation), and the Database-ViewController (which transforms a typical table view controller into a database-driven class). In the end, you’ll hook everything together with the app delegate.

The Database Class

Because there aren’t any preexisting object-oriented classes for the SQLite database functions, any program using a database should start by creating its own. The following listing contains the start of such a class, creating methods for the parts of the API that you’ll need to create the database view controller.

Listing 9.1. SKDatabase, a new sqlite3 database class

The header file (not shown) includes one variable declaration for the dbh (database handle) variable, the database handle. That’s the one variable you want to always have available to your class, because it gives access to the database. Now you’re ready to start working on the source code file.

The initWithFile: method uses some of the file commands that you learned in the previous section to find the database file, which is in the main bundle (but remember, you’ll want to copy this to the Documents directory if you make changes to your database). It then opens the file using sqlite3_open, the first of several sqlite3 API commands. Note that the NSString for the path has to be converted with the UTF8String method. This must be done throughout the class, because the SQLite API doesn’t use the Objective-C classes you’re familiar with.

The next few methods are pretty simple. close signals the end of the database life-cycle, dbh is a getter for the class’s one variable, and prepare turns an SQL statement into a prepared statement.

The lookupSingularSQL: method is where things get interesting, because it shows off the lifecycle of a complete SQL function call . Note that this function allows only a simple SQL call that returns one column from one row of information. That’s all you need for the database view controller, but you’ll doubtless need more complexity for a larger application.

The function starts by turning the SQL statement into a prepared statement . Then it steps to the first row. Depending on the type of lookup, it fetches either a string or an int. Finally, it cleans up the statement with a finalize.

In a more complex class, you’d doubtless want to write methods that execute SQL calls without any returns, that return multiple columns from a row, and that return multiple rows, but we’ll leave that for now (because we don’t need any of those features for this example) and move on to the menu class. The SQLite API has more information on these features if you need them.

The Menu Class

The next class, SKMenu, acts as an intermediary. At the frontend, it accepts requests for information about the menu that will fill the table view. On the backend, it turns those requests into SQL queries. It’s been designed in this way to create an opaque interface: you never have to know that a database is being used, just that the SKMenu class returns results for a table view.

The code of SKMenu is shown in the following listing. It mainly illustrates how to use the SKDatabase class in listing 9.1.

Listing 9.2. SKMenu, an interface to the SKDatabase class

Again, we haven’t shown the include file, but it includes one variable, myDB, which is a reference to the database object linked to the menu. The initWithFile: method initializes myDB by creating the database object.

The countForMenuWithParent: method is the first one to use the database . It gets a sum of how many menu items there are at a particular level of the menu hierarchy. contentForMenuWithParent: and integerForMenuWithParent: are two other lookup functions. The first looks up database entries that return strings, and the second looks up database entries that return ints. This is required because, as you’ll recall, SQLite has different database lookup functions for each of the variable types.

Finally, the dealloc method cleans up the database, first closing it and then releasing the object. It’s always important in Objective-C to keep track of which objects are responsible for which other objects. Here, the menu is responsible for the database, so it does the cleanup.

The Database View Controller

Now that you have some menu methods that allow a program to figure out the contents of a hierarchy of menus, you can put together your table view controller, which will read that information and fill table views on the fly. The next listing shows how the menu functions are used.

Listing 9.3. DatabaseViewController, a database-driven table view controller

To properly understand how the database view controller works, recall the menu format we introduced a few pages ago. Remember that each row of the menu has an individual ID (the catid) and a parentid that indicates what lies above it in the menu hierarchy. There’s also a title, which lists what the menu row says; a category, which indicates whether it leads to a new menu or is an end result; and an ordering variable. You use all that information in putting together your table view.

The database view controller is called multiple times by your project: once per menu or submenu. Each time, the initWithParentid:Menu: method identifies what level of the hierarchy to draw from the menu that’s enclosed . For example, if the parentid is 0, the top-level menu is drawn; if the parentid is 2, the menu that lies under entry (catid) 2 is drawn. The sole purpose of the init is to save that information.

You then have to fill in the standard table view controller methods. The count of sections is always 1 . The number of rows is calculated from the database, using the SKMenu’s countForMenuWithParent: method .

tableView:cellForRowAtIndexPath: is the first somewhat complex method . After the standard setup of the cell, the method looks up the title to be placed in the menu row. It then determines whether the menu row is a category; this affects whether the chevron accessory is placed.

Finally, tableView:didSelectRowAtIndexPath: does the fancy work . If the cell isn’t a category, it doesn’t do anything. (You’ll probably change this when creating another program, because you may want results to result in some action; this could be a great place to introduce a new protocol to respond when a result row is selected.)

If the cell is a category, magic happens. The database view controller creates a new database view controller, on the fly, using the same old menu. But the current catid becomes the new parentid, which means the new view controller contains all the rows that lie under the current row on the hierarchy. The new database view controller is then placed on the navigator controller’s stack, using the navigation methods you learned in chapter 7.

Figure 9.1 shows how all this fits together, using the database you created at the beginning of this section.

Figure 9.1. This menu was created directly from a database.

There’s one thing missing from this example—the app delegate.

The App Delegate

The app delegate needs to create the Navigator, initialize the menu object, build the first level of the menu hierarchy, and clean things up afterward. Listing 9.4 shows the couple of steps required to do this.

Listing 9.4. The app delegate that glues together these classes
-  (BOOL)application:(UIApplication *)application
     didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    (UIApplication *)application {
    myMenu = [[SKMenu alloc] initWithFile:@"nav.db"];
    DatabaseViewController *newController = [[DatabaseViewController alloc]
       initWithParentid:0 Menu:myMenu];
    newController.title = @"DB Menu";
    [self.navigationController pushViewController:newController
        animated:NO];
    [newController release];
    [window addSubview:[navigationController view]];
    [window makeKeyAndVisible];
    return YES;
}
- (void)dealloc {
    [myMenu release];
    [navigationController release];
    [window release];
    [super dealloc];
}

The applicationDidFinishLaunchingWithOptions: method sets things up. After initializing the menu, it creates the first database view controller and pushes it onto the navigation stack. The dealloc method later closes everything out. Note that it releases the menu object, which in turn will close the database and release that, ending the menu’s lifecycle.

Not shown here is the Xcode file, which includes one object, a navigation controller. Its standard view controller should be deleted, because you’ll be replacing it here.

Though it’s relatively basic, you now have a hierarchical menu of tables built entirely from a database.

9.1.5. Expanding this example

This example showed not only how to use databases in a real application but also how to put together a more complex project. Nonetheless, if you wanted to make regular use of the database and menu classes, you’d probably want to expand it more. We’ve already noted that SKDatabase could use more functionality and that the database view controller needs to do something for the result pages that it arrives on.

Because this is all database driven, you can also hand off considerable power to the users. It would be easy to expand this example so that users could create their own rows in menus and reorder the existing ones.

With SQLite now covered to the depth we can give it, we’ll move on to another major method of data retrieval, one of equal complexity: the Address Book.

9.2. Accessing the Address Book

Like SQLite, the Address Book is too complex to wholly document within the constraints of this chapter. It’s made up of two different frameworks—the Address Book framework and the Address Book UI framework—and together they contain over a dozen references. Fortunately, Apple offers an extensive tutorial on the Address Book: “Address Book Programming Guide for iOS.”

In this section, we’ll provide a basic reference that supplements Apple’s own tutorial, but we suggest you read their guide for more extensive information. We’ll look at the Address Book frameworks, show how to access the Address Book’s properties, and explain how to use the Address Book UI.

9.2.1. An overview of the frameworks

As noted, there are two frameworks for the Address Book. The Address Book framework contains what you’d expect: information on the data types that make up the Address Book and how to access them. The Address Book UI framework contains a bunch of handy interfaces that allow you to hand off the selection and editing of Address Book entries to modal view controllers that Apple has already written.

To use this functionality, you must include one or both frameworks, plus the appropriate include files: AddressBook/AddressBook.h and AddressBookUI/Address-BookUI.h.

Table 9.3 lists many of the most important classes in the frameworks.

Table 9.3. The Address Book classes, the framework they belong to, and what they do

Class

Framework

Summary

ABAddressBook Address Book Interface for accessing and changing the Address Book; may not be required if you use the Address Book UI framework
ABNewPersonViewController Address Book UI Interface for entering new record manually
ABPeoplePickerNavigationController Address Book UI Interface for selecting users and properties
ABPersonViewController Address Book UI Interface for displaying and editing records
ABUnknownPersonViewController Address Book UI Interface for displaying “fake” contact and possibly adding it to Address Book
ABGroup Address Book Opaque type giving access to the records of groups
ABPerson Address Book Opaque type giving access to the records of individual people
ABRecord Address Book Record providing information on a person or group
ABMultiValue Address Book Type containing multiple values, each with its own label; its precise use is defined in ABPerson, where it’s applied to addresses, dates, phone numbers, instant messages, URLs, and related names
ABMutableMultiValue Address Book An ABMultiValue whose values can be modified

Each of these classes contains numerous functions that can be used to build Address Book projects. We’ll talk about a few important functions and point you to the class references for the rest.

9.2.2. Accessing Address Book properties

As you’ll see shortly, the Address Book and Address Book UI frameworks ultimately provide different ways of accessing the Contacts data information: you might be working with the Address Book programmatically, or a user may be making selections through fancy UIs. Ways to select individual contacts may vary, but after a contact has been selected, you’ll generally use the same getter and setter functions to work with that record. These important functions are listed in table 9.4.

Table 9.4. Property setters and getters are among the most important functions in the Address Book.

Function

Arguments

Summary

ABRecordCopyValue ABRecordRef, property Looks up a specific property from a specific record
ABRecordSetValue ABRecordRef, property, value, &error Sets a property to a value in a record
ABMultiValueGetCount ABMultiValue Returns the size of a multivalue (which can contain one or more copies of a record, such as multiple phone numbers)
ABMultiValueCopyLabelAtIndex ABMultiValueRef, index Looks up the label of an entry in a multivalue
ABMultiValueCopyValueAtIndex ABMultiValueRef, index Looks up the content of an entry in a multivalue
ABCreateMutableCopy ABMultiValueRef Creates a copy of a multivalue
ABMultiValueReplaceLabelAtIndex ABMutableMultiValueRef, label, index Replaces a label at an index in a multivalue

Generally, when you’re using the getter functions for contacts in the Address Book, you’ll follow this procedure:

  1. Select one or more contacts through either the Address Book or the Address Book UI framework.
  2. To look at an individual property, like a name or phone number, use ABRecordCopyValue:
  3. If it’s a single-value property, you can immediately work with it as a string or some other class.
  4. If it’s a multivalue property, you need to use the ABMultiValue functions to access individual elements of the multivalue.

We included the setter methods in table 9.4 to keep the methods all in one place, but you’ll usually only be using the setters if you’re working with the Address Book framework, not the Address Book UI framework. Here’s how they work:

  1. Make changes to individual properties or to multivalues (using the mutable multivalue).
  2. Use ABRecordSetValue to save the value to your local copy of the Address Book.
  3. Use ABAddressBookSave to save your local changes to the real Address Book database.

We won’t cover the setter side of things (which you can find out about in the “Address Book Programming Guide for iOS”), but you’ll use many of the getter functions in the next section.

9.2.3. Querying the Address Book

Your first exploration of the Address Book will use the plain Address Book framework to access the Address Book and look up many of the values. This is shown in listing 9.5. It centers on a simple application with two objects built in Xcode: a UISearchBar and a UITextView (with an IBOutlet called myText).

You haven’t used search bars before, but they’re a simple way to enter search text. You set the search bar’s delegate and then respond to appropriate messages. In this case, your program responds to the searchBarSearchButtonClicked: delegate method and then looks up the information that was entered.

Listing 9.5. Looking up information in the Address Book

You start by running ABAddressBookCreate, which makes a local copy of the Address Book . You’ll need to do this whenever you’re working manually with the Address Book. After that, you use a few general Address Book functions that let you do things like count your number of contacts and groups. But it’s the search function that’s most important . This is one of two ways you can extract contacts from the Address Book by hand, the other being ABAddressBookCopyArrayOfAllPeople. Note the typing of searchBar.text as CFStringRef. This is a Core Foundation class equivalent to NSString *; you can find more information about the details of Core Foundation in the section “Using Core Foundation.”

The preceding steps are the major ones that differentiate working with the Address Book manually from working with it through a UI. With the Address Book framework, your program does the selection of contact records; with the UI framework, the user does it through a graphical interface. Beyond that, things work similarly via either methodology.

When you have a list of contacts, you need to extract individuals from the array . You can then use numerous functions to look at their properties. ABRecordCopyCompositeName gives you a full name already put together, and ABRecordCopyValue lets you pick out other properties. The list of properties and returned values is in the ABPerson reference.

Multivalues are only a little more difficult to use than simple properties. You use ABRecordCopyValue as usual , but then you have to work through the entire multivalue, which is effectively an associative array. The easiest thing to do is extract all the individual labels and values . This program displays the slightly awkward label names (for your reference), but you probably won’t usually want to show off words like $!<Mobile>!$, and it’s easy enough to strip them out.

The program ends by cleaning up some of the Core Foundation objects, using the standard Core Foundation memory-management functions. When you run it, this program displays some of the data from names that you search for, as shown in figure 9.2.

Figure 9.2. As shown here on the iPhone, the Address Book framework gives you low-level access to contact information.

You can do lots more with the Address Book, but this should outline the basics of how to access its several classes.

9.2.4. Using the Address Book UI

There are definitely times when you’ll want to work with the low-level Address Book functions you’ve seen so far. But you also don’t want to reinvent the wheel. If you need to let a user select, edit, or insert a new contact, you don’t need to program the UI. Instead, you can use the Address Book UI framework, which has all that functionality preprogrammed.

The Address Book UI framework contains only the four classes that we summarized in table 9.3: ABPeoplePickerNavigationController, ABNewPersonViewController, ABPersonViewController, and ABUnknownPersonViewController. Each of these UI objects is—as the names suggest—a view controller. To be precise, they’re highly specialized modal controllers that each assist you in a single Address Book–related task. Each controller also has a delegate protocol, which is how you link to a class that’s already pretty fully realized. We’ll touch on each of these classes, but we’ll give a lot of attention to only the people picker (ABPeoplePickerNavigationController).

The People Picker View Controller

To demonstrate the people picker, you’ll put together a quick utility with substantially identical functionality to the previous Address Book example. But rather than searching for multiple users using the Address Book framework, the user will instead select a specific user using the Address Book UI framework.

This program is built with a couple of Xcode–created objects. A UIToolBar with a single button allows the user to activate the program via the selectContact: method, and text will once more be displayed in a non-editable UITextView called myText. The program is shown in the following listing.

Listing 9.6. People picker: a simple, graphical way to select contacts

To instantiate a modal view controller, you follow three simple steps that are executed when the user clicks the appropriate button in the toolbar. You create the controller , set its delegate , and use UIViewController’s presentModalViewController: animated: method to place it at the top of your user’s screen . You then don’t have to worry about how the modal view controller looks or works; you just have to respond to the messages listed in the protocol reference.

The fully featured interface that’s available to you as soon as you pop up the controller is shown in figure 9.3.

Figure 9.3. A people picker view controller shown on both the iPad and iPhone

You do most of the work in the peoplePickerNavigationController:should-ContinueAfterSelectingPerson: method. This is called whenever a user selects an individual contact. Note that you can use a property of the peoplePicker variable to access the Address Book , which allows you to use many of the ABAddressBook functions without needing to create the Address Book manually. Beyond that, the people picker sends you an ABRecordRef for the contact that the user selected; from there, you work with it exactly as you worked with the ABRecordRefs you looked up in listing 9.5.

In this example, users can only select individual contacts, so when the method is done, you dismiss the modal view controller and then return NO, which tells the people picker that you don’t want to take the standard action for selecting the contact (which would be to call up a subpage with all of that contact’s properties).

If you wanted to let a user select a specific property from within a contact, you’d fill in the peoplePickerNavigationController:shouldContinueAfterSelecting-Person:property:identifier: method.

The third method defined by the ABPeoplePickerNavigationController protocol is peoplePickerNavigationControllerDidCancel:, which here causes the program to (again) dismiss the people picker.

You can do a little more with the people picker. As we already noted, you could have opted to let a user select an individual property by returning YES for the first shouldContinue method and then filling in the second one. You could also choose the individual properties that display on a contact page. Information on these possibilities is available in the ABPeoplePickerNavigationController and ABPeople-PickerNavigationControllerDelegate class references.

Using Core Foundation

The Address Book framework is the first framework you’ve worked with that requires you to use Core Foundation, a non-Cocoa library. This means you have to program slightly differently, as we promised would be the case back in chapter 1. The biggest differences are how variables and memory allocation work.

Core Foundation variables use different classes, such as CFStringRef replacing NSString *. Remember that the Core Foundation variable types usually have equivalents in Cocoa that you can freely switch between by casting, as is done in listing 9.5 when moving between the Address Book records and the UITextView text. When you’re using the Core Foundation variables natively, you have to use Core Foundation functions, such as CFArrayCount, to deal with them.

You also have to deal with memory management a little differently. Core Foundation memory management uses the same general approach as Cocoa Touch. There’s a reference count for each object that’s increased when it’s created or retained and decreased when it’s released. You have to remember slightly different rules for when you have a reference. If you create an object with a function using the word create or copy, you own a reference to it and must CFRelease it. If you create an object in another way, you don’t have a reference, and you must CFRetain the object if you want to keep it around. Some classes of objects may have their own release and retain functions. The “Memory Management Programming Guide for Core Foundation” tutorial at http://developer.apple.com has more information.

Core Foundation will show up again in chapter 12, where it controls some audio services, and in chapter 13, where it’s used for the Quartz 2D graphics package. You can use three other view controllers to allow users to interact with the Address Book, as we’ll discuss next.

The Other View Controllers

The other three view controllers work much like ABPeoplePickerNavigation-Controller, with one notable difference: they must each be built on top of a navigation controller. Technically, they’re probably not modal view controllers, because they go inside a navigation controller, but you can treat the navigation controller as a modal view controller once everything is loaded up, as you’ll see in the example.

The ABNewPersonViewController allows a user to enter a new contact. You can prefill some of the info by recording it in an ABRecordRef and setting the displayed-Person property, but this is purely optional (and probably won’t usually be done). After you’ve created the controller, you need to respond to a method that tells you when the user has entered a new contact. You don’t have to do anything with it except dismiss the modal controller, because the controller automatically saves the new contact to the Address Book. You can see what info the user entered, though, and do something with it if you want. The following listing shows how to deploy a new person view on top of a navigation controller and how to respond to its single method.

Listing 9.7. Functionality required to call up a new person view controller
-(IBAction)newContact:(id)sender {
    ABNewPersonViewController *myAdder =
        [[ABNewPersonViewController alloc] init];
    myAdder.newPersonViewDelegate = self;
    UINavigationController *myNav = [[UINavigationController alloc]
        initWithRootViewController:myAdder];
    [self presentModalViewController:myNav animated:YES];
    [myAdder release];
    [myNav release];
}
- (void)newPersonViewController:
    (ABNewPersonViewController *)newPersonViewController
    didCompleteWithNewPerson:(ABRecordRef)person {
    [self dismissModalViewControllerAnimated:YES];
}

The other two view controllers work the same way, except for the specifics about what methods each protocol defines.

The ABPersonViewController displays the information for a specific user. You’ll need to set the displayedPerson property to an ABRecordRef before you call it up. This ABRecordRef might have been retrieved from the Address Book search functions or from the people picker, using the functions we’ve already discussed. The person view controller can optionally be editable. There’s one method listed in the protocol, which activates when an individual property is selected.

Finally, the ABUnknownPersonViewController allows you to display the ABRecordRef defined by displayedPerson as if it were a real contact. Optionally, the user can create that information as a new contact, add it to an existing contact, or take property-based actions, like calling a number or showing a URL. It’s a great way to give users the option to add contact info for your software company to their Address Book.

You should now understand the basics of how to use the Address Book in your own programs.

9.3. An introduction to Core Data

The Core Data framework is a data storage system that was added to the iOS SDK 3.0. It provides a powerful and structured method to save and retrieve persistent data on the iPhone and iPad.

Core Data is based on the design methodology of Model-View-Controller. It’s intended as the model and provides such functionality. This allows the data to be completely separate from the views and controllers, giving the developer more control of their application.

Traditionally, when you wanted to save structured data on the device, you looked to such methods as SQLite or serialization. Core Data can be considered a hybrid of these two with some added functionality. It gives you the power of SQL with the simplicity of serialization.

Core Data allows you to take objects you already have in your application and save them directly into a database. You no longer need to do complex queries or make sure that your object property names match up with your database field names. Core Data handles these tasks for you.

Because Core Data is such a large topic, we’ll only scratch the surface of what’s possible. The next subsections will teach you how to use Core Data by walking you through a simple example of creating a to-do list application. Your application will display a table view of to-do objects that will be saved and retrieved using Core Data.

You’ll see how to set up Core Data, how to initialize Core Data objects, how to add those objects to the database, and then how to access and manipulate them. Although this won’t be an in-depth discussion about Core Data, it will give you the knowledge necessary to use Core Data for storage in your own applications.

9.3.1. Background information about Core Data

Let’s briefly look at the concepts and terminology we’ll be using. We’ll start by discussing the heart of Core Data, the managed object.

Managed Object

A managed object is a representation of an object you want to store in a database. Think of it as a record in SQL. It generally contains fields that match up with the properties of an object being saved in your application. After you create a managed object, you must insert it into a managed object context before you can save it to the data store.

Managed Object Context

The managed object context holds all of your managed objects until they’re ready to be committed to the database. Inside this context, managed objects can be added, modified, and deleted. This is like a buffer between your application and the database.

Managed Object Table

This object describes the schema of your database. It’s used when interfacing the managed object context with the database. A managed object table contains a collection of entity descriptions. Each of these entities describes a table in your database and is used when mapping managed objects to database entries.

9.3.2. Setting up Core Data in your application

Integrating Core Data into your application is simple. It requires less code than SQLite and offers a much simpler interface. The sample application we’ll look at in this section is a basic journal application. It will allow you to post entries and view them by date. To start using the Core Data API, be sure to add CoreData.framework to your project.

The first thing you must do to integrate Core Data is add the data model to your project. The data model file is where you do all the creation of your Core Data database. To add it, choose File > New File. Then, select Data Model under Resource. Name it something appropriate to your application. In this example, name it CDJournal.Xcdatamodel, and click Finish.

Now that you’ve added the data model to your project, you must define your database entities. Click the Xcdatamodel to open the table editor. Now, follow these steps to add the table for the journal entries:

  1. Click the + Add Entity button on the bottom of the editor area. Doing so adds a new entity with a default name. Change the name of this field to Entry. Figure 9.4 shows what this box should look like.
    Figure 9.4. Entry box

  2. Now that you’ve created an entity named Entry, you must create the properties that go along with it. To do this, click + in the Attributes box and add your attribute properties: in this case, body, title, and creationDate. Notice that you can specify the type of each property, similarly to how you’d do it in SQLite. The Property box should look like figure 9.5.
    Figure 9.5. Properties of an Entry

  3. At this point you may also create any other entities needed for your application. If one entity contains another, you can drag and drop to create relationships. This is similar to a foreign key in SQL. In this case, you could have authors, and an author could have many entries.

Now that you’ve created your database, you must generate the classes that represent your database objects. This allows you to get a code representation of your entities. To do this, select the entity in your Xcdatamodel file. Then, choose Editor > Create NSManagedObject Subclass. Figure 9.6 shows what this menu should look like. Accept the default path, and click Create. That’s it!

Figure 9.6. Creating a managed object class

When you’ve completed this process, you should see .h and .m files added to your project for the entity in your Core Data model. You may now use these class files like any other class in your project. You’ll see a little later how they’re used to interface with your database.

The last thing you must do to prepare your application is to add the Core Data framework to your project. To do this, in the project editor, select the target; in this case CDJournal is the target. Click Build Phases at the top of the project editor. Open the Link Binary With Libraries section. Then, click the + button and select core-data.framework from the list. Now you’re ready to start writing the code to initialize your Core Data model.

9.3.3. Initializing the Core Data objects

As with SQLite, Core Data requires quite a bit of setup before you can get it up and running. Fortunately, the code for doing this is standard and is roughly the same in most situations.

First, you must declare the objects needed by Core Data. As you did with SQLite, you declare them in your application delegate. This lets you send the context to only the classes that need to work with it. The objects you need to declare are model, context, and persistent store. The following listing shows this code.

Listing 9.8. Declaring the Core Data objects
#import <CoreData/CoreData.h>

@interface CDJournalAppDelegate : NSObject <UIApplicationDelegate/> {
    NSManagedObjectModel *managedObjectModel;
    NSManagedObjectContext *managedObjectContext;
    NSPersistentStoreCoordinator *persistentStoreCoordinator;

    UIWindow *window;
    UINavigationController *navigationController;
}

@property (nonatomic, retain, readonly) NSManagedObjectModel
    *managedObjectModel;
@property (nonatomic, retain, readonly)
    NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly)
    NSPersistentStoreCoordinator *persistentStoreCoordiantor;
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController
    *navigationController;

@end

Now that the properties have been declared, they must be initialized. After they’re initialized, only the managed object context will be used to interface with the data store. You must add a few methods to your delegate method to initialize all of these properties. They’re pretty standard and can be implemented the same way in all your applications. We’ll walk you through each of these methods.

The first method is the getter for the persistentStoreCoordinator. It’s where you’ll be loading and initializing the database used by your Core Data application. The next listing shows the code for this method.

Listing 9.9. Setter methods for Core Data objects

This is a fairly standard getter method. You first check to see if the store coordinator has already been initialized. If so, you return it. This is the case on every call following the first one to this method.

Next, you resolve the path to the database used by your application . As noted before, Core Data is built on top of SQLite. The name of the SQLite database you need to link to is the same as that of your Xcdatamodel file. In this case, it’s CDJournal.sqlite.

Finally, you initialize the persistent store coordinator with this path and the managed object model . In the event that an error occurs, the abort methods tell the application to fail and generate an error report. The last line returns a reference to the persistentStoreCoordinator object.

The next methods you’ll implement are the setters for the managedObjectContext and managedObjectModel properties. The following code shows how these methods are implemented.

Listing 9.10. Object model and object context getter methods
- (NSManagedObjectContext *) managedObjectContext {

    if (managedObjectContext != nil) {
        return managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator =
            [self persistentStoreCoordinator];
    if (coordinator != nil) {
        managedObjectContext = [[NSManagedObjectContext alloc] init];
        [managedObjectContext setPersistentStoreCoordinator: coordinator];
    }
    return managedObjectContext;
}

- (NSManagedObjectModel *)managedObjectModel {

    if (managedObjectModel != nil) {
        return managedObjectModel;
    }
    managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil]
     retain];
    return managedObjectModel;

}

Both of these methods check to see if their property has been initialized. If not, they’re initialized and returned. The final method you need to implement is applicationWillTerminate. This is where you’ll save your managed object context to the data store. You save your context every time the user makes a change to the data. This code is needed in case some unsaved data is lying around when the application exits. The following listing shows the code for this method.

Listing 9.11. Saving the managed object context
- (void)applicationWillTerminate:(UIApplication *)application
{
    NSError *error = nil;
    if (managedObjectContext != nil) {
        if ([managedObjectContext hasChanges] &&
            ![managedObjectContext save:&error]) {
                    NSLog(@"Unresolved error %@, %@", error,
                           [error userInfo]);
                    abort();
        }
    }
}

The first thing this method does is check to see if your managedObjectContext is nil. This will most likely never be the case, but it’s still good practice to check. Next, you check to see if there are any unsaved changes to the context by calling the hasChanges method. The context will have changes anytime something is added, modified, or deleted.

If changes are present, the context is saved by calling the save method. Similarly to the code in listing 9.9, you call the abort method in the event of an error in order to generate a crash log. You’re now ready to make changes to your data.

9.3.4. Adding objects to the database

As you’ve seen, to work with database objects in SQLite, you must write the raw SQL code. Also, every time you want to add a record, you must write many lines of code that can’t be reused in other areas. This is where the true power of Core Data comes in.

In Core Data, you’re working with only the class files that were generated from your data model. This allows you to manipulate them as you would any other object in Objective-C. Let’s start by looking at the code to add an object to the database. The code for adding a new entity to the database is shown here.

Listing 9.12. Adding an entity to the database
Entry * e = (Entry *)[NSEntityDescription
   insertNewObjectForEntityForName:@"Entry"
   inManagedObjectContext:managedObjectContext];

[e setTitle:textField.text];
[e setBody:textView.text];
[e setCreationDate:[NSDate date]];
NSError *error;

if (![managedObjectContext save:&error]) {
    NSLog(@"Error Saving: %@",[error description]);
}

As you can see, the only difference here is how you initialize the Entry object. Instead of doing a [[Entry alloc] init], you allow Core Data to create a new object inside the context. After this object has been created, you can begin using its accessor and mutator methods. You can even create your own methods inside these objects and call them.

When you’re ready to save your managed object, you call the save method of the managed object context. This causes your changes to be made permanent by writing them to the database.

9.3.5. Fetching, updating, and deleting objects in Core Data

To update or delete objects from the database, you must first have them in memory. To do this, you need to fetch them into an array.

Fetching in Core Data is much more elegant than it is in SQL. You tell Core Data what objects you want and how to sort the objects, and it returns them in an array with little code. The code to do a simple fetch on your journal entries and sort them by their creation date is shown here.

Listing 9.13. Fetching data

The first step in retrieving results from the database is to create the fetch request. After the request has been created, you must set its entity. The entity represents which object type you’re retrieving. In this case, the entity is an Entry of your journal.

After your request is created, you must tell it how to sort the results. If you omit this step, the ordering of the results will be undefined. This means the results returned could be in any order. The sort descriptor you create tells the request to sort the results by the creation date in ascending order. You can sort based on any field in your entity.

The last thing you need to do is execute the request . Notice that the request returns an NSMutableArray. This array contains all the objects retrieved from the database in the order specified by the sort descriptor. To keep these results around, you set them to a class variable .

When you have an array of objects on hand, you can begin modifying or deleting them. Let’s look at modifying objects. Here’s how you update a managed object:

- (void) update:(Entry *) entry {
    [entry setTitle:textField.text];
    [entry setBody:textView.text];
    [entry setCreationDate:[NSDate date]];

    NSError *error;
    if (![self.managedObjectContext save:&error]) {
        NSLog(@"Error Saving: %@",[error description]);
    }
}

As you can see, the code is almost identical to the code to add a new entry. The only difference is how the entry is retrieved. Instead of letting Core Data allocate a new entry for you, you modify one you already have on hand. Typically, this is first retrieved from the array you created in listing 9.13.

As with updating, you must have a managed object on hand in order to delete it. You can’t delete a managed object without first retrieving it. Here’s how you delete a managed object from the database:

-(void) delete:(Entry *) entry {
    [managedObjectContext deleteObject:entry];

    [entries removeObject:entry];
    NSError *error;
        if (![managedObjectContext save:&error]) {
         NSLog(@"Error deleted entry %@",[error description]);
    }
}

The delete method is fairly straightforward. The first thing to do is remove the object from the managed object context. Any time the context is saved after removing the object, it deletes that object from the data store.

Next, you delete the object from the global array of entries so you can reflect the update to the user. If you don’t do this, the user may still see the object in a table view, even though it’s deleted forever when the application exits.

Finally, you save the context. As with any changes made to the context, saving it makes them permanent.

9.4. Summary

As you’ve seen, you have two powerful options to consider when storing large amounts of data on the iPhone and iPad. SQLite is great for anyone with prior experience with SQL and MySQL. You have the ability to use full SQL syntax to work with the records without having to learn a new design pattern.

Core Data is Apple’s response to solving the complexities associated with SQL. You no longer need to know complicated SQL syntax in order to have a fully functional database in your application. Core Data extracts much of the process and gives you high-level objects to work with as you please.

In the next chapter, we’ll move away from data storage and work with some of the cool hardware features of the iPhone and iPad. These include the accelerometer, GPS, and compass.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.198.174