Learn Objective-C on the Mac - Mark Dalrymple [95]
In this example, we actually have three conditions, joined together by ORs, and wrapped in parentheses just like you 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 you 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 you do wildcard matching, and MATCHES lets you use regular expressions to do advanced comparisons. However, the last of those, 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 you really need a fixed query for some special purpose in your application, but sometimes you’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 you just saw. For example:
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 Interface Builder
Let’s try one of the most basic ways to put a predicate to use: attaching it directly to a controller in Interface Builder. Go back to your MainMenu.xib file in Interface Builder, select the FoundQuotes controller in the main nib window, and bring up the Attributes Inspector. At the bottom, you’ll see a text view labeled Fetch Predicate, where you can simply add some text to define a predicate. Try entering this:
Then save your changes, switch back to Xcode, and click Build & 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 your 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, in Mac OS X 10.5 and later, that does just that!
With NSPredicateEditor, you 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 9-6.
Figure 9-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,