Aug 032013
 

I recently had a recruiter ask me if I had experience with UICollectionView. Coincidentally, I had just read about that a day or two before. Now, that’s not really experience that gets you the gig so I threw together a sample app that used UICollectionView.

The app, Image Fool, implements an unusual, and not very useful, way of browsing images on Flickr. When first launched, it shows 26 images, the first of which matches the Flickr image search for ‘a’ and the second for ‘b’ and so on.

imageFool

The problem I had was that it was slow, I mean, really slow. To show the 26 images I need to hit the Flickr server 26 times fast.

The first thing I tried was the standard Grand Central Dispatch method of putting your slow operations on a thread and then doing your UI work on the main thread.

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // switch to a background thread and perform your expensive operation
    dispatch_async(dispatch_get_main_queue(), ^{
        // switch back to the main thread to update your UI
    });
});

This code snippet is from Ray Wenderlich’s wonderful iOS blog. I like how with just 2 comments┬áthe author, Marcelo Fabri, ┬átells us what goes in the first part and what goes in the 2nd dispatch_async.

I immediately realized I wanted finer control over my threads, both the number of concurrent operations and their priority. That meant I should use NSOperation.

for (int i = 0; i < kIMAGE_FOOL_MAX_CELLS; i++) {
        __weak IFViewController *weakSelf = self;
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            IFFlickrPhoto *newFlickr = [[IFFlickrPhoto alloc] init];
            [newFlickr loadFlickrPhotos:searchString];
            [self.collectionView reloadData];
        }];

        [operation setQueuePriority:NSOperationQueuePriorityVeryLow];
        [self.flickrImageQueue addOperation:operation];
}

And when I setup the queue I told it to perform only 2 concurrent operations:

    self.flickrSearchQueue = [[NSOperationQueue alloc] init];
    self.flickrSearchQueue.maxConcurrentOperationCount = 2;

Performance was still bad. After a lot of head scratching I realized that my call to reloadData wasn’t happening at the right time. I tried putting the reloadData onto the main thread.

dispatch_async(dispatch_get_main_queue(), ^{
    [self.collectionView reloadItemsAtIndexPaths:indexPaths];
});

And it was fast enough but each image was redrawing like crazy over and over again. Image flicker in a Flickr program is not desirable.

And then the lightbulb went on, I was asking it 26 times to reload the data, that was ludicrous. I needed 26 cells to reload, but each of them only once. That’s where the reloadItemsAtIndexPaths method is useful

dispatch_async(dispatch_get_main_queue(), ^{
    [self.collectionView reloadItemsAtIndexPaths:indexPaths];
});

Of course I had to figure out the indexPaths but that’s easy in this case, I have a for loop and the index path just contains the index of the for loop.

And now the UICollectionView cells scroll smoothly. You can download the full source from github.