iPhone/iPad UISearchBar and UISearchDisplayController Asynchronous Example

All of the Apple examples and almost everything I could find on the net showing how to use UISearchBar and UISearchDisplayController operated on an existing set of data in a data structure in the application. I want to search either a database locally on the iPhone/iPad or make a remote search request to an internet API. Here are my notes on how to get this working.

First it is necessary to understand the elements involved. UISearchBar is the user interface widget. The bar with the text field with rounded sides. You can use this by itself and connect its delegate to something that implements UISearchBarDelegate.

But, beginning with iPhone OS 3.0, there is a purportedly better way: UISearchDisplayController. There’s no need to subclass it, just use it out of the box. It is the glue between various parts of the search process. You initialize it with a UISearchBar and a “contentsController”. While the Apple docs state that the contents controller is usually a UITableViewController, it doesn’t have to be, and I think it’s simpler to understand what’s going on if you just use a UIViewController.

The UISearchDisplayController becomes the delegate for the UISearchBar. You need to wire up a few more connections: a UISearchDisplayDelegate, a UITableViewDataSource that provides the search result data, and a UITableViewDelegate for handling selecting the search result table cells.

Here’s what UISearchDisplayController does in a typical search scenario:

  1. The user taps the UISearchBar. UISearchDisplayController activates the search interface, brings up the keyboard. It creates a table view for the results.
  2. The user enters text to search for. UISearchDisplayController informs the UISearchDisplayDelegate of this. The UISearchDisplayController asks the delegate if it should reload its table (the default is YES).
  3. When the UISearchDisplayController wants to reload the table, it asks the UITableViewDataSource for the number of rows and the cells for each row it wants. It displays the results table view on top of the existing controller.
  4. If a user selects one of the rows in the table view owned by the UISearchDisplayController, it lets the UITableViewDelegate handle it.

The key point (at least for me) to understand here is that UISearchDisplayController creates its own UITableView. It puts it on top of the contentsController when there are search results. With all the examples I found, the contentsController was itself a UITableViewController and had its own UITableView that it was displaying.

The basic examples all work like this: there’s a UITableViewController with 200 rows of data. There’s a UISearchBar and a UISearchDisplayController. All of the delegates for the UISearchDisplayController connect to the UITableViewController. When the search is done, the UISearchDisplayController overlays its table with a filtered set of the original rows on top of the existing table of 200 rows. When the search interface is dismissed, the overlay table is removed and the original table is available.

Clearly these basic examples don’t work for a large database or an internet search API. You can’t load all the results into a table data source before the search and just filter them for the search results. You need to make a request for the search results, and to keep your app responsive, you should make that request asynchronously.

Apple hints at how to do this in their documentation for UISearchDisplayDelegate in the searchDisplayController:shouldReloadTableForSearchString: and searchDisplayController:shouldReloadTableForSearchScope:.

You might implement this method if you want to perform an asynchronous search. You would initiate the search in this method, then return NO. You would reload the table when you have results.

For this example, I’ll create a new project in xcode, an iPad Split-view based application. Open MainWindow.xib in Interface Builder, remove the RootViewController. It’s a table view controller and doesn’t need to be for this example. Drag a standard view controller into its place.

Back in xcode, go ahead and trash RootViewController.m and RootViewController.h. Remove references to it in the app delegate and the details controller. Add a new file, a UIViewController subclass with “Targeted for iPad” checked. Name it GenericViewController.

In interface builder, change the class of the view controller you added to GenericViewController. Add a view to GenericViewController. Drag the “Search Bar and Search Display Controller” to fit underneath the view. Doing this in Interface Builder makes it automatically connect the search display controller to the GenericViewController for all its delegates. This is fine for this example, but feel free to change this wiring if you want.

You can build it now, it will compile without errors and run. But if you try to search for anything, it will crash. This is because GenericViewController is set to be the delegate for all kinds of things for the search display controller, but none of the required protocols are implemented. Start by adding the protocols to the interface in GenericViewController.h:

@interface GenericViewController : UIViewController <UISearchDisplayDelegate, UITableViewDataSource, UITableViewDelegate> {
    
}

And in the implementation, paste this in for now (copied from the RootViewController default):

- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView {
        // Return the number of sections.
        return 1;
}

- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
        // Return the number of rows in the section.
        return 10;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        
        static NSString *CellIdentifier = @"CellIdentifier";
        
        // Dequeue or create a cell of the appropriate type.
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
        if (cell == nil) {
                cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
                cell.accessoryType = UITableViewCellAccessoryNone;
        }
        
        // Configure the cell.
        cell.textLabel.text = [NSString stringWithFormat:@"Row %d", indexPath.row];
        return cell;
}

Now you can build it and when you search, it always returns the same “Row 1 - 10” data, but you can see UISearchDisplayController doing its work and overlaying a table view of results.

Enough synchronous stuff. Start the asynchronicity! For the purposes of this demo, we’re going to fake a slow search by using an NSTimer. Here’s the crucial method to add to GenericViewController:

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
        [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(mockSearch:) userInfo:searchString repeats:NO];
        return NO;
}

By returning NO, the search display controller won’t reload its data. The timer calls mockSearch after a two second delay. Here’s mockSearch:

- (void)mockSearch:(NSTimer*)timer
{
        [_data removeAllObjects];
        int count = 1 + random() % 20;
        for (int i = 0; i < count; i++) {
                [_data addObject:timer.userInfo];
        }
        [self.searchDisplayController.searchResultsTableView reloadData];
}

The _data variable is an NSMutableArray member variable. It shoves a random number of rows containing the search string (in userInfo) into the array, then tells the search display controller’s table view to reload its data. I changed these methods to use _data for the rows:

- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
        return [_data count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        
        static NSString *CellIdentifier = @"CellIdentifier";
        
        // Dequeue or create a cell of the appropriate type.
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
        if (cell == nil) {
                cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
                cell.accessoryType = UITableViewCellAccessoryNone;
        }
        
        // Configure the cell.
        cell.textLabel.text = [NSString stringWithFormat:@"Row %d: %@", indexPath.row, [_data objectAtIndex:indexPath.row]];
        return cell;
}

So that’s about it. To use it in the real world, change the NSTimer call to some asynchronous network or database call. When it is done, reload the results table just like in mockSearch. One nice thing would be to also implement searchDisplayControllerDidBeginSearch: and searchDisplayControllerDidEndSearch: to display some sort of “Loading…” view on top of the results view until the results come in.

Update: By popular demand, here is the full source and xcode project for this example:

SearchExample.tar.gz

It’s for iPhone OS 3.2 and above only.

Ruby Jekyll LSI Classifier Fixes

It was a bit of a pain to get the LSI classifier feature of jekyll to work with rb-gsl. The gem doesn’t work and there’s a bug in the classifier gem. Here’s what I did to get it working:

  1. Check out the latest rb-gsl from svn: svn checkout svn://rubyforge.org/var/svn/rb-gsl/trunk
  2. Build and install that (simple instructions in the README)
  3. Install the classifier gem
  4. Change the classifier-1.3.1/lib/classifier/lsi.rb file from this

Around line 290:

def build_reduced_matrix( matrix, cutoff=0.75 )
  # ...
  # Reconstruct the term document matrix, only with reduced rank
  u * Matrix.diag( s ) * v.trans
end

To this:

def build_reduced_matrix( matrix, cutoff=0.75 )
  # ...
  # Reconstruct the term document matrix, only with reduced rank
  if $GSL
    u * GSL::Matrix.diag( s ) * v.trans
  else
    u * Matrix.diag( s ) * v.trans
  end
end

Without rb-gsl, the jekyll lsi classifier is way, way too slow to use after you get more than a handful of posts.

Running Only One Shoulda Rails Test

To run only one test in a shoulda test file, use a regular expression to match the shoulda context. If you have a context like:

context "parsing German Klavier" do
  setup do
    #...
  end
  
  should "have the correct parsed data" do
    # lots of assertions
  end

  should "validate the definition" do
    # more assertions
  end
end 

in a file full of other contexts, you can just run this context by doing:

ruby test/unit/word_test.rb -n /Klavier/

Much easier than looking for the results of one failing test in a list of hundreds…

Go wrapper for Tokyo Dystopia

To go along with the Tokyo Tyrant Go wrapper I wrote, I also wrote a Tokyo Dystopia Go wrapper.

I’m using it for the fast online foreign language dictionary on Langalot. So far, it’s blazing fast.

Go wrapper for Tokyo Tyrant

Just released a little open source Go wrapper for Tokyo Tyrant:
gotyrant.

It’s far from complete…it only does what I’ve needed so far for a side project, but it could be useful for somebody.

SPF Records with Rails and ActionMailer

An SPF record is a DNS record that works like a reverse MX record. MX records tell the internet what machines can receive mail for a domain, SPF records specify which machines can send mail for a domain. They aren’t required by any means, but they are a good idea to help mail pass spam filters.

There is no such thing as an SPF record in the DNS spec, they are just TXT records. There are several online tools to help you create the right TXT record for your specifications.
I used openspf.org. For example, for one of my servers, it generated the following SPF record:

"v=spf1 a ~all"

This basically means that only the A record for this domain is allowed to send mail. The ~all means to “soft” deny mail from all other hosts. Changing it to -all would make it a “hard” deny.

If you also use google apps for mail for a domain, you can also add include:aspmx.googlemail.com, so it will look like:

"v=spf1 a include:aspmx.googlemail.com ~all"

This means the A record server(s) can send mail for this domain as well as all the google servers.

If you use rails, you might have to do some other work. Say you have a domain mycoolapp.com and have setup up your SPF record correctly. A mail recipient will most likely look at the Return-Path header when validating the SPF record. I always use sendmail as the ActionMailer delivery method:

ActionMailer::Base.delivery_method = :sendmail 

and by default, ActionMailer will send it as the user running the script or application.
So if a cron job is running under the runner account and the default hostname for your server is mistral.example.com (or mail.mycoolapp.com, but not mycoolapp.com), the Return-Path will end up being runner@mistral.example.com. Thus, even if with your correct SPF record, the mail recipient will be looking for an SPF record for mistral.example.com.

This is easy to fix by setting a header in the ActionMailer. For example:

def verify(subscription)
  subject "Verify your subscription"
  recipients subscription.email
  from "Some Robot <nobody@mycoolapp.com>"
  body :subscription => subscription
  headers 'return-path' => 'nobody@mycoolapp.com'
end

This will cause ActionMailer to add -f nobody@mycoolapp.com when it calls sendmail and the return path will be set correctly.

To verify everything is working well, send an email to a gmail account and view the original message. If you see something like:

Received-SPF: pass (google.com: domain of nobody@mycoolapp.com designates 99.99.99.99 as permitted sender) client-ip=99.99.99.99;
Authentication-Results: mx.google.com; spf=pass (google.com: domain of nobody@mycoolapp.com designates 99.99.99.99 as permitted sender) smtp.mail=nobody@mycoolapp.com

Then everything is set up perfectly!

Autotest with Rails Tip -- Ignore Files for Less CPU Usage

By default, autotest polls every file in your rails project for changes. By telling it to ignore certain files, you can drastically reduce its CPU usage. It looks for a config file named .autotest either in the working directory or your home directory. For a rails project, my .autotest file looks like this:

Autotest.add_hook :initialize do |autotest|
  %w{.git .DS_Store ._* vendor}.each do |exception|
    autotest.add_exception(exception)
  end
end

You can also use autotest-fsevent (more info here), which eliminates the polling altogether.

CIFilter on iPhone? Nope.

If you look in CALayer.h in the iPhone SDK, you’ll see these properties:

/* An array of filters that will be applied to the contents of the
 * layer and its sublayers. Defaults to nil. Animatable. */

@property(copy) NSArray *filters;

/* An array of filters that are applied to the background of the layer.
 * The root layer ignores this property. Animatable. */

@property(copy) NSArray *backgroundFilters;

While the reference manual is (sort of) clear that the CIFilter class is unavailable:

filters

An array of CoreImage filters that are applied to the contents of the receiver and its sublayers. Animatable.

Special Considerations

While the CALayer class exposes this property, Core Image is not available in iPhone OS. Currently the filters available for this property are undefined.

It gets confusing because their example code (in the iPhone doc set) uses a CIFilter:

// The selection layer will pulse continuously.
// This is accomplished by setting a bloom filter on the layer
 
// create the filter and set its default values
CIFilter *filter = [CIFilter filterWithName:@"CIBloom"];
[filter setDefaults];
[filter setValue:[NSNumber numberWithFloat:5.0] forKey:@"inputRadius"];
 
// name the filter so we can use the keypath to animate the inputIntensity
// attribute of the filter
[filter setName:@"pulseFilter"];
 
// set the filter to the selection layer's filters
[selectionLayer setFilters:[NSArray arrayWithObject:filter]];

But before you get your hopes up, don’t bother trying this. It won’t compile…

Create new git repository with gitosis

Assuming gitosis already set up and working…

Edit gitosis-admin/gitosis.conf and add the new repository.

[group company]
members = jackblack
writable = examproj

Commit this and push it.

git commit -a -m "added examproj"
git push

In a local repository (you’ve already called git init and done your initial commit), push the repository to the server.

git remote add origin git@code.example.com:examproj.git
git push origin master:refs/heads/master

Then clone it to where you want it.

cd ~/git
git clone git@code.example.com:examproj.git

git tags

Some notes on how to use tags in git:

You should always use the -a switch (or sign it). Explanation here.

To tag and the push to remote:

git tag -a v1.0.0 -m "version 1.0!!!"
git push --tags

Then, in the future, to get the code at the tag point and do stuff with it:

git checkout -b fromtag1.0 v1.0.0
<do some stuff>
git commit ...

And then back in the master, you can merge that branch back in:

git checkout master
git merge fromtag1.0
git push

That’s enough for now…

See a list of all posts in The Archive »
You should follow me on Twitter: @patrickxb