In a perfect world, we could create software that gave instant responses to user input. Unfortunately, this is not always feasible; some things take time, and so we have to keep the user waiting. There are some broadly-accepted conventions as to how to do this politely:
- Give the user an idea of how long the process will take
- Display an indicator that work is ongoing
- Provide updates on the computation’s progress
- Do not block (as much as possible) the function of UI elements
- Allow the user to cancel the computation (if feasible)
Today, I’d like to present some tips on how to follow these conventions on the iPhone.
I’m basing this article on the
LoadController class in my Full-Text Search demo project. I used that class to handle the initial setup of the FTS database, which takes about 1s per 3000 characters of database size.
That class isn’t a perfect example of what I’m going to be talking about, primarily because it doesn’t update the UI during loading. This was a design choice I made; it was a lot faster (30%) to load the data with a single opaque Core Data operation than with multiple, more transparent ones. That said, the concepts are well-illustrated by the class, and you can even see where I commented out the updates.
Our first problem is to move the long-running computation off the main thread; this thread drives the UI, and if we dive into a multi-second (or multi-minute) computation on that thread, our program will appear to freeze until the computation finishes. Fortunately, Cocoa makes it easy to invoke object methods on other threads with the
performSelectorInBackground:withObject: method of
NSObject. The effects of this method are pretty much what you’d expect; the only gotcha is that the method you run on the background thread must create (and, when it terminates, release) its own
Now that we’ve launched our computation on the background thread, how can we update our UI to let the user know what’s afoot? Straight UI calls from the background thread won’t work, due to complex issues involving Run Loops. Instead, we must arrange to update the UI from the main thread, and, again, Cocoa helpfully provides the
performSelectorOnMainThread:withObject:waitUntilDone: method to do this.
If you want to stop the user from manipulating the program in the normal way while your computation is running, an effective technique is to place a view on top of the normal display. Assuming that you’ve created an appropriate view, and conveniently stored a pointer to it in the
self.view variable, the following code will execute the overlay:
self.view.frame = [[UIScreen mainScreen] applicationFrame]; [[(YourAppDelegate*)[[UIApplication sharedApplication] delegate] window] addSubview:self.view];
(This assumes that your app’s delegate has a
window member; most of them do.)
When you want to dismiss the overlay, just execute this code: