Real-time updates of iPhone UI
I would like to share with you a problem that has been a proverbial pain in the bum for me and has been nagging me for quite some time. As an application developer the last thing you want to have is people waiting for your application without showing them some kind of progress. For my work, I’m currently developing an iPhone application for the MACE portal that allows users to access content, more specifically images, related to a building in their vicinity. For each building, the images available are shown in a grid view. Thus, this involves a lot of thumbnail images to be downloaded from a webpage and thus can lead to some lags in the software. To give the users some feeling of progress I decided I wanted to load the thumbnails and show them in the UI as soon as they were download. While it sounded like a trivial problem, it took me quite some time to solve and by sharing it I hope to save you all some time. Shortly the problem can be broken down in a couple of steps:
- Creating a method to update the UI from the main thread
- Setting up a thread to download thumbnail images in the background & sending updates to NSNotificationCenter from this thread
- Creating a method to receive notification updates and registering this method with NSNotificationCenter
Below I will provide some examples on how to solve these problems and provide you with the necessary code. Most of it, I already figured out for my self, but the final step to get it working was given by this post.
Creating a method to update the UI from the main thread
First, in the UIViewController that is attached to the UIView you would like to update create a method that will do all the UI updates for you. This is mainly because you want to do all UI updates from the main thread controlling the UI. In my case this involved adding UIButtons with the thumbnail images to a UIScrollview, which resulted in the following code:
-(void)addThumbnailButton:(UIImage*)thumbnailImage
{
//find the thumbnail image that was added last
int lastIndex = [self.thumbnailImages count] - 1;
//add button with thumbnail at a certain position in the grid
UIButton* button = [self buildButton:thumbnailImage atPos:lastIndex];
[self.buttons addObject:button];
//add the button to the UIScrollView
[self.view addSubview:button];
//tell the view to update itself
[self.view setNeedsDisplay];
}
Apart from the buildbutton method that creates a UIButton and calculates its position in a grid I think this code is fairly trivial. Of course, you should replace this method with one that does the updating you need.
Setting up a thread to download thumbnail images in the background
As I said before, in my case the operation that took most time was downloading thumbnail images from a website. Such time-intensive operations you would normally want to do in the background, and provide the user with timely updates to indicate progress. Most probably, reading this article you will also need to do some time-intensive operations, possibly also some web requests. All these operations need to go into the thread that you can setup with code similar to this:
-(void)loadPics
{
//each thread should have its own autorelease pool for memory management
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
//start the activity spinner in the main thread to show the user something is being done
[self performSelectorOnMainThread:@selector(turnActivitySpinnerOn) withObject:nil waitUntilDone:NO];
//get content from the website
self.contentItems = [self.contentItemModel getContentItemsForObject];
//retrieve all thumbnail images from the webservice and draw image grid
for (ContentItem* ci in self.contentItems)
{
//download image from website
UIImage* thumbnailImage = [self getThumbNailImage:ci];
//add image to an array
[self.thumbnailImages addObject:thumbnailImage];
//notify the listener that a thumbnail has been downloaded
[[NSNotificationCenter defaultCenter] postNotificationName:@"ThumbUpdate" object:thumbnailImage];
}
//stop the activity spinner in the main thread to show downloading the thumbnails has finished
[self performSelectorOnMainThread:@selector(turnActivitySpinnerOff) withObject:nil waitUntilDone:NO];
//clean up the memory allocated in this thread
[pool release];
}
This method is fairly less trivial but the functionality can be split up in three parts: (1) showing something is done by the software with an activity spinner (2) downloading the actual images from the webservice, and (3) notifying the listener that a thumbnail has been downloaded.
First, for turning on and off an activity spinner, I created two methods in the main thread turnActivitySpinnerOn and turnActivitySpinnerOff. Before I start downloading the thumbnails I invoke the turnActivitySpinnerOn method with the following code:
[self performSelectorOnMainThread:@selector(turnActivitySpinnerOn) withObject:nil waitUntilDone:NO];
which is used to invoke a method call on the main thread from another one. Similarly, after downloading has finished I invoke the turnActivitySpinnerOff method.
Second, I download the thumbnails from the website. The specifics of this are not very interesting to you but there’s one thing to note. Downloading is done in a loop and for each iteration off the loop the following code is called:
[[NSNotificationCenter defaultCenter] postNotificationName:@"ThumbUpdate" object:thumbnailImage];
The code above tells the notification centre that some event has occurred with the name “ThumbUpdate” that should be send to all of the listeners that have been registered for this event. The notification is furthermore sent with the thumbnailImage that has been downloaded. This bring us to our next point.
Creating a method to receive notification updates and registering this method with NSNotificationCenter
We want the UI to be updated on the basis of the updates/events sent by the download thread, and we can handle this by adding the following code to our UIViewController:
- (void)notify:(NSNotification *)notification
{
UIImage* img = [notification object];
[self performSelectorOnMainThread:@selector(addThumbnailButton:) withObject:img waitUntilDone:YES];
}
This was where things went wrong in my case. I thought by adding the notify method to the same class (the UIViewController) as the code that adds the buttons, both methods would be run in the same thread. Apparently, this is not so, and the notify method is run in the NSNotificationCenter thread, which is the logical thing to do too. Therefore, we need to add the performSelectorOnMainThread function, that calls the method we created above, with the thumbnail that has just been downloaded. The thumbnail will be added and we are almost done. Finally, we should register this method with the notification centre with the following code, that I added to the UIViewController’s viewDidLoad method, but you might add somewhere else. Note that I also spawn our thread to load the thumbnail images here.
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notify:) name:@"ThumbUpdate" object:nil];
//start thread to load all thumbnail images and add them to an image grid
[NSThread detachNewThreadSelector: @selector(loadPics) toTarget:self withObject:nil];
[super viewDidLoad];
}
And to clean things up nicely, add the following to your dealloc method:
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"ThumbUpdate" object:nil];
}
which de-registers your view controller as a listener for ThumbUpdate events. Anyway, hope this helps, and of course I’m open to questions and suggestions.


