Chapter    10

Search and Retrieve Core Data with Criteria

The MythBase examples in Chapters 8 and 9 demonstrated the basics of how Core Data works, letting us create, retrieve, update, and delete objects from a backing store with a minimum of fuss. So far, we’ve been working primarily with full data sets in MythBase. For most of the entities we’ve used (MythicalPerson, MythicalBand, and MythicalVenue), all the objects for each entity are loaded during application startup and kept in memory for the entire life of the application. That’s okay for an application like MythBase, which maintains a small database, but what if one of our main entities consists of thousands or millions of instances in a backing store? Our app would load everything from storage when the app launches, which would probably lead to the app filling up all available memory, swapping to disk, and so on. Apart from that problem, we’d also probably end up with a bad user experience because a GUI that’s navigable when it’s displaying twenty objects in a table view may become impossible to use effectively when there are thousands of entries.

The solution to this problem involves an important piece of functionality: a way to provide some search criteria so that we can limit the objects pulled out of storage by a controller. This chapter will introduce the use of NSPredicate to restrict searches to just a subset of all the objects for a given Entity. We’ll specify a hard-coded NSPredicate, both in Interface Builder and in our source code, and we’ll let our users define the values for a predicate using NSPredicateEditor.

Creating QuoteMonger

We’ll demonstrate the use of NSPredicate by creating an app called QuoteMonger, which allows us to keep track of all our favorite shows and famous quotes from them. It will consist of a Core Data application with two entities, plus a GUI that supports both data entry and flexible querying. Figure 10-1 shows a view of the completed application.

9781430245421_Fig10-01.jpg

Figure 10-1. QuoteMonger’s data entry and search windows

Note  By now you should have experience with the basics of creating Xcode projects and files, so we’re not going to continue giving exact click-by-click instructions for some of the things you’ve already done several times, instead reserving the highest level of detail for the new topics we’re covering in each chapter.

Creating the Project and Its Data Model

Start by creating a new Core Data project in Xcode and naming it QuoteMonger. Then edit the QuoteMonger.xcdatamodel file, and add two entities called Show and Quote. We walked through this in the previous two chapters, so please flip back a few pages to review if you get stuck.

Give Show a single attribute called name, of type String. Turn off the Optional checkbox, and turn on the Indexed checkbox. Then give Quote two String attributes called quoteText and character. For each of those, turn on the Indexed checkbox, but only turn off the Optional checkbox for quoteText, leaving it on for character (because it’s conceivable that you can’t quite remember or determine who said something, but want to store it as a quote anyway).

Finally, make a one-to-many relationship from Show to Quote (remember, that means making two relationships—a to-many from Show to Quote called quotes and a to-one from Quote to Show called show—and configuring them to be each other’s inverse). The resulting data model should look like something like Figure 10-2.

9781430245421_Fig10-02.jpg

Figure 10-2. The data model for QuoteMonger

The Data Entry Window

With the data model in place, it’s time to start building the GUI. The interesting part, which this chapter is mostly about, is the search window. But we can’t do any searching until we have some data to search, so we’ll start by creating the portion of the GUI that lets us do data entry. We’re going to make a very basic GUI for this—displaying shows and quotes in a single window and where only quotes that are tied to the currently selected show are displayed.

A Window in Two Parts

Double-click MainMenu.xib in the Xcode project to open it in an Interface Builder pane. This brings up the standard empty application nib we’ve used previously. Select the NSWindow instance in the main nib window, and use the resizing control to make it taller (about twice as tall as it started out). We’ll need this space because we’re going to put two sets of views in here, one for the Show entity and one for the Quote entity.

Showing Shows

We’ll start with a view for the Show. Drag out an NSTableView from the Object Library and position it near the top of the window. Resize it to take up most of the space. Don’t worry about the blue guidelines this time; in fact, make it a bit smaller than the guidelines would suggest. We’re going to group it into a box with some other controls, so the sizing isn’t critical yet. Open up the Attributes Inspector, and make the table view view-based with one column. Resize the column itself to take up the width of the table view, and set the column’s name to “Name.” Drag out a button and position it below the lower-right-hand corner of the table view, and label it “Add.” Drag out another button of the same time and position it to the left of the Add button, and label that one “Remove.” Select all three of those controls, and group them into a Box (select Editor image Embed In image Box). Set the title of the Box to be “Show.” Now we can adjust the size of the Box relative to the window, and then adjust the table view inside the Box to get a pleasing arrangement.

Now that we’ve got a place to list the shows, we need to connect them to the core data model, and we’ll use Cocoa bindings and an NSArrayController as the glue. Drag out an Array Controller from the Object Library, and title it “Shows.” In the Attribute Inspector, set the Mode to be “Entity Name” in the pop-up menu, and then enter “Show” in the Entity Name field to connect it to the Show entity in the Model Editor. Check the checkbox for Prepares Content, which will instruct the Array Controller to load all the Shows at launch time. Switch to the Bindings Inspector. Bind the Managed Object Context (at the bottom of the bindings list) to App Delegate, and set the Model Key Path to “managedObjectContext.”

For the bindings, we’ll start with the table view. Select the table view and then open the Bindings Inspector. Expand the Content section under Table Content, and check the checkbox next to Bind to. The pop-up menu should read “Shows”. The default Controller Key of “arrangedObjects” is what we want, and there’s nothing to go in Model Key Path. We also want the array controller to know about the selected row, so bind the Selection Indexes binding to Shows and set the Controller Key here to “selectionIndexes.” Then drill down in the view hierarchy within the table view, using the object dock, down to the “Static Text - Table View Cell.” In the Attributes Inspector, change the Behavior to Editable. Then, in the Bindings Inspector, bind the Value to the Table Cell View and set its Model Key Path to “objectValue.name.”

That covers the table view. To finish up the part of the window dealing with Shows, we need to wire up the buttons. This is done just like in the last chapter—with a combination of target-action for handling a click and Cocoa bindings for enabling or disabling the button as appropriate. We’ll do the target-action first. Control-drag from the Add button to the Shows array controller, and select add: as the received action. Do the same thing for the Remove button, this time selecting remove: as the received action. Select the Add button again, and go to the Bindings Inspector. Check the Bind to checkbox to connect the button to the Shows controller, and enter “canAdd” as the Controller Key. Repeat with the Remove button, binding it to the “canRemove” Controller Key.

The window should look like Figure 10-3. Note the Outlets Inspector view in the utility area, showing the bindings and target-action connections for the Array Controller. This view can be really useful for troubleshooting when things don’t work as expected!

9781430245421_Fig10-03.jpg

Figure 10-3. Partial layout for the data entry window

Quoting Quotes

Now we’re going to repeat most of that same setup below the Show box, for Quotes. As before, drag out a table view and two buttons, positioning them in the bottom half of the window and titling the buttons as done previously. For this table view, however, we’re going to leave it with two columns. Change it to view-based, as before, and check the checkbox labeled Alternating Rows, which will make the content somewhat easier to read. Title the left column “Quote Text” and the right column “Character,” and resize them to give the Quote Text column some more room. In the Quote Text column, double-click the “Table Cell View” text inside the column, and change it to “Quote”. Do the same thing to the “Table Cell View” text in the Character column, changing it to read “Character.” This will help us tell the two columns apart in just a moment. Group these three controls into a box, as before, and title the box “Quote.”

This part of the window will also use an NSArrayController, but it’ll be configured a bit differently than the Shows array controller. Drag out a controller, and title it “Quotes.” In the Attributes Inspector, set its Mode to be Entity Name, and then set the Entity Name field to Quote (the same name as the entity that we defined in the Model Editor). Then, switch to the Bindings Inspector. By default, each array controller will fetch all objects for the corresponding entity. We’re going to change this one to only fetch quotes based on the selected Show. Open the Content Set binding, select “Shows” in the pop-up list, “selection” in the Controller Key combo box, and type in “quotes” in the Model Key Path combo box. Finally, press Return to turn on the binding. We also need to connect it to a managed object context, which is provided by the App Delegate. Bind the Managed Object Context (at the bottom of the bindings list) to App Delegate, and set the Model Key Path to “managedObjectContext.”

Now we can bind this table view to the Quotes controller. Select the table view and then open the Bindings Inspector. Expand the Content section under Table Content, and check the checkbox next to Bind to. The pop-up menu should read “Quotes.” The default Controller Key of “arrangedObjects” is what we want. As before, the array controller should be told about the selected row, so bind the Selection Indexes binding to Quotes, and set the Controller Key here to “selectionIndexes.”

To bind the two columns, drill down in the view hierarchy within the table view, using the object dock, down to the “Static Text – Quote” entry. In the Attributes Inspector, change the Behavior to Editable. Then, in the Bindings Inspector, bind the Value to the Table Cell View, and set its Model Key Path to “objectValue.quoteText.” Switch to the “Static Text – Character” entry in the object dock, change its Mode to Editable, and then bind it to the Table Cell View and set its Model Key Path to “objectValue.character.”

Lastly, we need to wire up the buttons. From each of the buttons, control-drag out to the Quotes controller, setting the action to add: or remove: as appropriate. Bind the enabled binding of each button to the canAdd or canRemove Controller Key on the Quotes controller, as appropriate.

The layout should be similar to that in Figure 10-4.

9781430245421_Fig10-04.jpg

Figure 10-4. Completed layout for the data entry window

Entering Some Initial Quotes

Save the changes and click Run. The app should start up and show the Data Entry window. Select the Add button below the upper table view to add a show, and double-click in the highlighted space in the table view to edit the show’s name. Do this a few times to create a few Show instances. Now, with one of the shows selected, add a quote in the lower table, editing the text and the names of any characters right into the table. If you add a quote that contains an exchange between two or more characters, enter the names of all involved characters in the Character field. When we later enable search based on character names, it will work with all the names you entered. Add a few more quotes, spread across a few different shows. Note that the list of quotes changes when you select a different show, and if you quit and restart QuoteMonger, you should see that everything you entered has been saved.

If, on the other hand, nothing seems to work, check the debugger log in Xcode and see if the application raised an exception. Common problems are neglecting to set up the bindings for the Managed Object Context on the Array Controllers, or mistyping the name of a Controller Key or Model Key Path binding.

Creating the Quote Finder Window

Now it’s time to lay the foundation for the search window. Back in the Interface Builder pane in Xcode, drag out a new window from the Object Library and drop it on the canvas. Xcode offers a variety of window types, but we want a normal plain window. (You’ll probably also want to close the QuoteMonger window to get it out of the way, and you can do this by clicking the little x just above the upper left corner of the window on the Interface Builder canvas.)

Select the new window and go to the Attributes Inspector. Title the new window “Quote Finder.” This window will have two visible controls on it: a table view and a text view. The table view will display a list of matching quotes, and the text view will display the contents of a selected quote. Later on, we’ll also use a new one called a predicate editor to define the search criteria. The predicate editor is used when defining filtering rules in Mail.app and creating Smart Playlists in iTunes. It’s a general-purpose Cocoa component, and we can play with it too. However, we’re not going to use it just yet.

Let’s get started! Drag out a Table View from the Object Library, and position it near the top of the window. In the Attributes Inspector, set it to be view-based and give it three columns. Title the three columns “Quote Text,” “Character,” and “Show,” respectively. In addition, edit the text in each field to reflect the name of the column. We can also set the Alternating Rows checkbox for this table view in case the search returns a lot of matches. This table view won’t be going into a box so we can resize it to fill out the width of the window, letting the blue guidelines tell us where to stop.

Now, drag out a Text View from the Object Library (enter “text view” into the Search field at the bottom of the library area), and position it below the table view. Expand this one to be the same width as the table view. In the Attributes Inspector, turn off the Editable and Rich Text checkboxes. For Find, set the pop-up menu to Uses Bar and check the Incremental Searching checkbox. This will allow the big text view to use the embedded search bar that was introduced in Mac OS/X 10.7. The window should look something like Figure 10-5.

9781430245421_Fig10-05.jpg

Figure 10-5. First pass at the Query Finder window

Next, drag out an Array Controller and name it “FoundQuotes.” In the Attributes Inspector, set its Mode to be Entity Name and give “Quote” as the Entity Name to use. Check the checkbox by Prepares Content as well. Above these settings, in the Array Controller block of the Attributes Inspector, check the Auto Rearrange Content checkbox. This will cause it to correctly reload and refilter its contents whenever the user makes changes in the Quotes controller. In the Bindings Inspector, bind its Managed Object Context to the App Delegate and set the Model Key Path to “managedObjectContext.”

Now we need to set up bindings for the user interface controls—the table view and the text view. We’ll start, as usual, with the table view. Bind the Content and Selection Indexes bindings on the table view to the FoundQuotes controller. Bind the Content binding to the arrangedObjects Controller Key, and bind the Selection Indexes binding to the selectionIndexes Controller Key. Then, drill down to the “Static Text - Quote Text” field inside the table view. Configure its Value binding to the Table Cell View, and use “objectValue.quoteText” as the Model Key Path. Configure the “Static Text – Character” field’s Value binding to the Table Cell View, and use “objectValue.character” as the Model Key Path. Lastly, select the “Static Text – Show” field within the table view, and set its Value binding to the Table Cell View, using “objectValue.show.name” as the Model Key Path.

For the text view, it’s a bit simpler. Select the text view, and bind its Value binding to FoundQuotes. Use “selection” as the Controller Key, and “quoteText” as the Model Key Path.

Now click Run, and note the two windows. The new Query Finder window simply shows all quotes we’ve entered with the data entry window. The rest of this chapter will cover how to change that using an NSPredicate so that only the quotes a user searches for show up in this window.

Limiting Results with NSPredicate

As mentioned, we can limit which records an NSArrayController prepares for display by using an NSPredicate. We can assign a predicate to an array controller directly in Interface Builder; from within our application code during initialization or whenever conditions change, warranting a refetch; or through Cocoa Bindings, which means that changes to a predicate can be automatically propagated to a controller. We’ll explore all of these options in this chapter.

Creating Predicates

The simplest way to create an NSPredicate is by using a format string containing attribute names, comparators, and values to compare against. The definition of a predicate looks a lot like a WHERE clause in SQL and serves much the same purpose. Predicates are not limited to just Core Data usage and can be applied to other areas of Mac OS X such as Spotlight. In its most basic form, we can define an NSPredicate like this:

NSPredicate *p = [NSPredicate predicateWithFormat:
    @"(quoteText CONTAINS[cd] 'missed') OR "
     "(character CONTAINS[cd] 'kramer') OR "
     "(show.name CONTAINS[cd] 'trek')"];

Note  In C, if you have multiple inline string constants in your code separated only by whitespace (including carriage returns) they will all be concatenated together into a single character array, which can help format long strings in your code. This trick works equally well for inline NSString constants—just put a single @ symbol before the first string, as shown previously.

In this example, we actually have three conditions, joined together by ORs and wrapped in parentheses just like we might do in application code. Each of these conditions uses the CONTAINS comparator (which does exactly what you might guess), with some options specified inside square brackets. The c makes the comparison case-insensitive, while the d makes the comparison diacritic-insensitive. An equality comparison specifying both, for example, will consider “ramon” and “Ramón” to be equal.

CONTAINS is just one of several comparators available within the predicate. All attributes can use the =, <, >, >=, <=, !=, and BETWEEN comparators. (Note that ==, =>, =<, and <> are equivalent to =, >=, <=, and !=, respectively.) String attributes can use the BEGINSWITH, CONTAINS, ENDSWITH, LIKE, and MATCHES comparators.

Note  These should mostly be self-explanatory, with a couple of notable exceptions: BETWEEN lets us specify a pair of lower and upper bounds, so the value to the right of it should be substituted in with a two-item NSArray; LIKE lets us do wildcard matching; and MATCHES lets us use regular expressions to do advanced comparisons. However, MATCHES doesn’t work with an SQLite backend, so it’s not much use when fetching values from a Core Data store.

The hard-coded option is fine if we really need a fixed query for some special purpose in our application, but sometimes we’ll want to create a query based on user input or other current data. Fortunately, the NSPredicate class provides an easy way to interpolate values using the same predicateWithFormat: method we just saw. For example,

// Assume these variables exist and point at valid objects
NSString *quoteInput;
NSString *characterInput;
NSString *showNameInput;
NSPredicate *p = [NSPredicate predicateWithFormat:
    @"(quoteText CONTAINS[cd] %@) OR "
     "(character CONTAINS[cd] %@) OR "
     "(show.name CONTAINS[cd] %@)",
    quoteInput, characterInput, showNameInput];

When that code runs, the values of the three variables will be put into the resulting predicate. Note that the %@ markers in the format string are not surrounded by single-quotes, as the bare values were in the previous example.

Specifying an NSAppController’s Predicate in Xcode

Let’s try one of the most basic ways to put a predicate to use: attaching it directly to a controller in Xcode. Go back to the MainMenu.xib file in Xcode, select the FoundQuotes controller in the main nib window, and bring up the Attributes Inspector. At the bottom is a text view labeled Fetch Predicate, where we can simply add some text to define a predicate. Try entering this:

show.name CONTAINS[cd] 'trek'

Then save the changes, switch back to Xcode, and click Run. Now the search window won’t necessarily show all the quotes you’ve entered. If you’ve entered some Star Trek quotes, it will show only those, but if you haven’t entered any Star Trek quotes, you’ll now see nothing in the search window. Of course, it’s possible you’ve only entered Star Trek quotes, in which case this view will be just the same as it was before. In that case, enter some quotes from another show to verify that the predicate is filtering them out (and also because, really, there’s more to television than just Star Trek).

User-Defined Predicates

The nib-defined predicate is fine for special uses, where some part of the GUI should always show a particular subset of the data, but what we’re after is the ability to let the user define the search parameters themselves. Ideally, they should be able to choose multiple parameters to search on, edit the values to compare against, and even change the comparator itself (instead of just using CONTAINS all the time). Fortunately, Cocoa provides a GUI control called NSPredicateEditor that does just that!

As we hinted at earlier, with NSPredicateEditor, we can make a GUI that works a lot like the Smart Playlist feature in iTunes or the Smart Mailbox feature in Mail. Users can add and remove search criteria, and the results will update on the fly. See Figure 10-6.

9781430245421_Fig10-06.jpg

Figure 10-6. QuoteMonger’s predicate editor in action

Both NSPredicateEditor and NSArrayController can set and retrieve the value of their NSPredicate via a binding, so what we’ll do is add an NSPredicate as a property of the app delegate and make the appropriate bindings. Then, when the user makes any changes in the predicate editor, the updated predicate will automatically be passed along to the array controller.

Adding a Predicate to the App Delegate

Start by adding a new property to the app delegate. Open the QMAppDelegate.h file, which looks just like the default app delegate header that was generated for MythBase. Add a new property called searchPredicate. The interface declaration should now look something like this (new lines are in bold):

@interface QMAppDelegate : NSObject <NSApplicationDelegate>

@property (assign) IBOutlet NSWindow *window;

@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;

@property (strong) NSPredicate *searchPredicate;

- (IBAction)saveAction:(id)sender;

@end

Now switch over to QMAppDelegate.m and add the following code near the top, just after the @synthesize directives generated by Xcode. The new lines are in bold.

@implementation QMAppDelegate

@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize managedObjectContext = _managedObjectContext;

#define DEFAULT_PREDICATE @"(quoteText CONTAINS[cd] 'space') OR "
                           "(character CONTAINS[cd] 'Knight')"

- (id)init
{
    if ((self = [super init])) {
        self.searchPredicate = [NSPredicate predicateWithFormat:DEFAULT_PREDICATE];
    }
    return self;
}

The init method simply creates a default value for the searchPredicate property. Now let’s hook up our controller to use this new predicate. Go back to the Interface Builder canvas, select the FoundQuotes controller, and bring up the Attributes Inspector. Select all the text in the Fetch Predicate text view and delete it, then switch to the Bindings Inspector. In the Controller Content Parameters section, open the Filter Predicate binding info. Select App Delegate in the pop-up, type “searchPredicate” into the Model Key Path combo box, and press Return to turn on the binding.

Save the work, go back to Xcode, and click Run. You should now see a different set of results: quotes spoken by a character named “Knight” or containing the word “space.” If nothing is matching, add a quote with one of these characteristics in order to test out the search window.

Add a Predicate Editor to the Search Window

We now have the FoundQuotes controller fetching values based on the contents of a predicate that is “owned” by the app delegate. The next step is to add an NSPredicateEditor to the search window and configure it to let the user edit the predicate.

In Xcode, bring up the Quote Finder window. Make the whole window a bit taller, and drag the existing table view and text view down to the bottom. Then find an NSPredicateEditor in the Object Library, drag it into the empty space at the top of the window, and resize it to make it fill the available space. Figure 10-7 shows the idea.

9781430245421_Fig10-07.jpg

Figure 10-7. The predicate editor is in place in our Search window

Configuring the Predicate Editor

Now it’s time to bind the editor to the app delegate’s predicate instance. Select the NSPredicateEditor (don’t forget the extra click to select the editor itself, not the scroll view that contains it), open the Bindings Inspector, and then examine the Value binding info. Select App Delegate in the pop-up, then type in “searchPredicate” in the Model Key Path combo box, and press Return to activate this binding.

At this point, there’s only one more thing we need to do in order to enable searching with this predicate editor: we have to tailor it to the attributes we want to search on. NSPredicateEditor is a quite complex control, and fortunately most of its interesting features can be configured directly in Xcode. The predicate editor displays one or more NSPredicateEditorRowTemplate objects, which can each be configured to do searching in a variety of ways. We can make row templates that allow us to specify numbers or dates to compare against object values, or to pick from a list of predefined strings. In our case, we’re going to configure a row template that lets the user type a string in a text field to search by character names, show names, and quote contents. This row template can be reused, allowing the user to specify multiple search criteria simultaneously. In addition, another row template will let the user choose whether the search criteria must all be met (Boolean AND) or whether it’s enough that any match succeeds (Boolean OR) for a quote to appear in the results.

In the Interface Builder canvas, drill down into the predicate editor by clicking on the lower of the two visible rows (the one containing pop-up buttons showing name and contains). With that row template selected, bring up the Attributes Inspector. Note the checkboxes that let us choose which comparators we’ll allow the user to use, plus the pop-up buttons that let us choose the nature of the expressions used on either side of the comparator (key paths, strings, constant values, and the like). The default setup, with key paths on the left and strings on the right, is perfect for our purposes, but we do need to adjust the key paths for our searching needs.

Edit the three default values that are listed below Left Exprs key paths, changing them to quoteText, character, and show.name. Next, click to turn on the Case Insensitive and Diacritical Mark Insensitive checkboxes (see Figure 10-8).

9781430245421_Fig10-08.jpg

Figure 10-8. Configuring an NSPredicateEditorRowTemplate

Then, examine the pop-up button in the row template itself. This will show three entries with names the same as what we just entered for key paths. Change these to more human-readable names: “Quotation,” “Character Name,” and “Show Name.”

Now click the upper row template, the one showing “Any of the following are true.” The configuration for this is very simple. Checkboxes let us choose to allow Boolean AND, OR, and NOT for the user to search on. Enable all of those to allow the most utility.

Now there’s just one more piece of configuration that we need to do. By default, NSPredicateEditor lets the user delete all the rows, right down to the last one, at which point there is no longer a + button to add any back in. Change this by selecting the predicate editor itself (not one of the row templates) and clicking to turn off the Can Remove All Rows checkbox in the Attributes Inspector.

Save the work, hit Run in Xcode, and bask in the glory of QuoteMonger! We can now easily search through all saved quotes, using the three criteria we configured in the predicate editor.

Saving a Predicate

Before we call it a day, let’s add one final bit of polish that will make using QuoteMonger a friendly experience for our users. Right now, each time a user launches the app, it starts up with our silly default query. Wouldn’t it be nice if it instead showed the last query from the previous time it was running? It turns out that this is a piece of cake. NSPredicate has a handy method called predicateFormat that returns the predicate’s value in the form of a string. So, we can save the current searchPredicate’s string representation using NSUserDefaults when the user quits the app and check for a saved string when the app launches. Open up QMAppDelegate.m, and make the following changes to the first part of its implementation (just add all the lines that appear in bold):

#import "QMAppDelegate.h"

@implementation QMAppDelegate

@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize managedObjectContext = _managedObjectContext;

#define DEFAULT_PREDICATE @"(quoteText CONTAINS[cd] 'missed') OR "
                           "(character CONTAINS[cd] 'kramer')"
#define STORED_PREDICATE_KEY @"storedPredicateFormat"

- init
{
  if ((self = [super init])) {
    NSString *format = [[NSUserDefaults standardUserDefaults]
        objectForKey:STORED_PREDICATE_KEY];
    if (format)
      self.searchPredicate = [NSPredicate predicateWithFormat: format];
    else
      self.searchPredicate = [NSPredicate predicateWithFormat: DEFAULT_PREDICATE];
  }
  return self;
}

- (void)applicationWillTerminate:(NSNotification *)aNotification
{
  NSString *format = [self.searchPredicate predicateFormat];
  [[NSUserDefaults standardUserDefaults] setObject:format forKey:STORED_PREDICATE_KEY];
}

We start by defining a string, which will be used as a key to store and retrieve the predicate from the user’s preferences using NSUserDefaults. Then, we enhance the init method to look for a stored predicate. It first checks to see if a stored predicate string exists, and if it does, it populates the searchPredicate instance variable with a new predicate created from the format string. If there is no stored predicate, the default predicate is created instead.

Finally, we implement the applicationWillTerminate: method. This method is called automatically when the user quits the application, giving us a chance to do some final cleanup. Here, we convert the current search parameters (maintained in the searchPredicate instance variable) into a string, and save that string into the user’s preferences so that the same search will pop up the next time the user runs the app.

Save, run, and then modify the search terms. Quit the app and run it again, and the previous search terms show up again.

Wrap Up

This chapter showed the basics of how to use NSPredicate to narrow down a Core Data result set. We saw how an NSPredicate can be constructed in code, or in Xcode, or by user interaction with NSPredicateEditor. We even got a hint of how these predicates can be saved for later use, much like iTunes and Mail do with smart playlists and smart folders. These techniques can help you easily bring new levels of functionality to your own apps.

Chapters 8 through 10 covered the main concepts you need to get up and running with Core Data. Now it’s time to move on to other topics. We won’t be leaving Core Data behind, however, as it will still play a role in some further exercises, though not to the same extent. We don’t have enough space in this book to turn every example into a full-fledged application. Even in cases where we’re not using Core Data, however, you might want to think about where it could fit into what we’re demonstrating in the coming chapters. Now that you’ve got Core Data at your fingertips, you’ll probably have a whole new view on some aspects of application development that you would have solved in a different way before. Speaking of views, that’s where we’re headed in Chapter 11, with a look at some of the most prominent and widely used view components in Cocoa and elsewhere: windows, menus, and sheets.

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

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