oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Creating Toolbars for Mac OS X
Pages: 1, 2, 3, 4


Getting the search item to function requires some heavy modification to the way records are stored in the address book application. Rather than lead you through a detailed, step-by-step description of every change that was made, I will give you an overview of what’s going on and leave you with the code to download and study. The project folder that has all of the additions made can be downloaded here.

As it stands now, the individual records are stored in an instance of NSMutableArray assigned to the instance variable records. Whenever information is displayed in a tableview, records is the provider of that information via the NSTableDataSource methods.

Now, suppose we want to restrict the information displayed in the tableview to some subset of records based on the search criteria. How would we go about obtaining and using a subset of records as a data source without modifying the contents of records? The way we go about this is to introduce two more NSMutableArray instance variables called subset and activeSet. To see where activeSet occurs in the code, do a search for it in the Find panel.

Rather than using the variable records in the data source methods, we use activeSet. The trick is that the NSMutableArray object assigned to activeSet changes depending on the function being performed. When the application loads, activeSet is set to equal records immediately after a mutable array has been assigned to records. In this way both activeSet and records point to the same object in memory. Two variables, one object.

Now when we perform a search, we create another instance of NSMutableArray and assign it to the variable subset. The search will consist of enumerating records and comparing the first and last name strings in the record dictionary to the search string. If there is a partial match, then that record is added to subset. After records has been completely enumerated, we assign activeSet to subset, and the tableview will then display the contents of subset -- that is, the search results. When the search field is cleared, activeSet is once again set to equal records.

Also in Programming with Cocoa

Understanding the NSTableView Class

Inside StYNCies, Part 2

Inside StYNCies

Build an eDoc Reader for Your iPod, Part 3

Understand now that when we speak of adding objects to subset, we’re actually adding the pointers to the particular objects to subset, and subset is sending retain messages to those objects. So now both records and subset have ownership over a shared set of objects. This is the same “two variables, one object” situation we had above, except that the variables are elements of subset and records. This is significant to the way the tableview works. In particular, when you edit information directly in the table, that information is not changed solely in records contained in subset (via activeSet); the changes are also reflected in object.

Here again is how it works: When records is first initialized, we set activeSet equal to it. When we search we assign activeSet to subset. Finally, in the search method we will have an if-statement checking to see if the length of the search string is 0. If so, then we reassign activeSet to records.

So that’s what’s going on with our change of variable names and introduction of new instance variables. How do we make the search function work? Recall that in Interface Builder we set NSController to be the delegate of the text field in searchItemView. The reason we did this is so we could implement a useful delegate method of NSControl called -controlTextDidChange:, which is invoked whenever the text in the text field changes. Thus, a new search will be performed after each letter is typed in the search field.

Add to Controller.m the following definition of -controlTextDidChange:

- (void)controlTextDidChange:(NSNotification *)aNotification
    NSString *searchString = [[[aNotification object] stringValue] lowercaseString];
    NSEnumerator *e = [records objectEnumerator];
    NSString *fnString, *lnString;
    id object;

    if ( [searchString length] == 0 ) {
	activeSet = records;
	[tableView reloadData];

    [subset release];
    subset = [[NSMutableArray alloc] init];

    while ( object = [e nextObject] ) {
	fnString = [[object objectForKey:@"First Name"] lowercaseString];
	lnString = [[object objectForKey:@"Last Name"] lowercaseString];
	if ( [fnString hasPrefix:searchString] || [lnString hasPrefix:searchString] )
	    [subset addObject:object];
    activeSet = subset;
    [tableView reloadData];

What we have here is an enumerator for records that checks every record contained in that array to see if the search string is a prefix of either the first name value or the last name value. If so, we add it to subset and continue enumerating. Once the enumeration has completed we set activeSet to point to subset and then tell tableView to reload its data.

There are a couple of things to note here. First, we make the search case insensitive by sending lowercaseString messages to all of the strings involved and use the returned strings in the actual search.

Additionally, note the argument of this method, NSNotification. This is a class which we have not discussed yet in this series of columns. If you read the class documentation on this method in the NSControl and NSTextField specifications you will see that the controlTextDidChange: method is called in the delegate by a notification. A notification is a way of communicating between objects who have no knowledge of each other using a broadcast paradigm. The way it works is that objects can register themselves with a notification center, which is an instance of the class NSNotificationCenter. By registering itself in this way, an object becomes an observer which responds to only certain named notifications. In this registration process the object indicates which one of its methods will be invoked in response to the notification.

You register your object with the default notification center using the method -addObserver:selector:name:object:. Here observer is the object to respond to the notification, selector is the method to be invoked in the observer, name is the name of the notification which observer shall respond to, and the object argument is used to restrict which notifications the observer can respond to. To respond to all notifications with the given name, pass nil as this argument. Otherwise, you can have your observer respond only to notifications that have been posted by an object indicated in this argument.

Conveniently, by assigning NSController as the delegate of the search text field, this has all been taken care of for us behind the scenes. All we had to do was implement -controlTextDidChange:.

The other side of the story with notifications is the object posting a notification. Objects post notifications to the notification center using either the -postNotification: or the -postNotificationName:object: of NSNotificationCenter. In the second method object is often self. When a notification is posted to the notification center it is then broadcast to all observers of that notification. The notification center acts as a middle man in this one-way communication between two stranger objects.

So, back to our search method. The object which sent the notification can be accessed by passing an object message to the notification object. This is how we gain access to the text field whose contents have changed and retrieve the string contained within.

Now, with this method added to your code, and the needed changes made as described above, you should have a cool little Search function in your application.

This concludes learning how to make toolbars. As always, I highly recommend reading all of the relevant class documentation as I have only touched on a few possibilities. See you next time!