Loading Images in UICollectionViewCell: Naive to the Clever.

I’m working on an iOS app called Scenery. It pulls images from /r/earthporn via imgur’s API. It’s a simple 2 screen app that uses UICollectionViewCell to display images in a grid format. I went through a few code iterations in getting the images to display. Let’s go through them.

2014-02-05 12.19.28 2014-02-05 12.19.36

Iteration 1: The “Hello World” of downloads

NSURL *url = [NSURL URLWithString:@"https://api.imgur.com/3/gallery/r/earthporn/time/"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];

This works as expected except that it’s a synchronous request. This means the screen is blank to the user until all the images have been downloaded and displayed. In this case, each API request to imgur is returning 52 images making for a poor user experience.

Iteration 2: The Sensible Asynchronous Method

What’s obviously required here is to first load the screen and then download the images asynchronously.

        // download the image asynchronously
        NSString *imageString = [[_imgurArray objectAtIndex:indexPath.row] objectForKey:@"link"];
        NSURL *imageURL = [NSURL URLWithString:imageString];

        [self downloadImageWithURL:imageURL
                   completionBlock:^(BOOL succeeded, UIImage *image) {
            if (succeeded) {
                    imageView.image = image;
                }
            }
        }];

I’m now downloading the images asynchronously with downloadImageWithURL:. Great! Now the screen loads first and I can see the images pop up one by one as it finishes the download. However, every time I scroll down the screen and up again, the app re-downloads and re-requests the images again. Not great.

Note that all of this action is taking place in – (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath.

Iteration 3: The Sensible Asynchronous Method with Makeshift Cache

So to deal with the multiple requests to imgur’s server each time a user scrolls up and down, I loaded all the images into a NSMutableDictionary as a sort of cache.

    if ([_cachedImages objectForKey:identifier] != nil) {
        imageView.image = [_cachedImages valueForKey:identifier];
    } else {
        // download the image asynchronously
        NSString *imageString = [[_imgurArray objectAtIndex:indexPath.row] objectForKey:@"link"];
        NSURL *imageURL = [NSURL URLWithString:imageString];

        [self downloadImageWithURL:imageURL
                   completionBlock:^(BOOL succeeded, UIImage *image) {
            if (succeeded) {
                if ([_cachedImages objectForKey:identifier] == nil) {
                    [_cachedImages setValue:image forKey:identifier];
                    imageView.image = [_cachedImages valueForKey:identifier];
                }
            }
        }];
    }

Getting there! The scroll speed is smooth and the download is asynchronous. But look, what do we have here

Screen Shot 2014-02-05 at 12.40.16 pm Screen Shot 2014-02-05 at 12.40.49 pmThe memory footprint is out of this world. On first load, the app consumes 300MB. When I load the 2nd page, it also doubles. Each subsequent page load adds roughly 200MB. As you might have guessed, it crashes once I tried running this on my iPhone.

Note: the original code was heavily inspired by this post: http://natashatherobot.com/ios-how-to-download-images-asynchronously-make-uitableview-scroll-fast/

Iteration 4: The Real Sensible Method

We’re all standing on the shoulders of giants in the software industry. Image caching and url requests are a well trodden path that have been solved by other folks much smarter than myself. A cursory search reveals a few prominent libraries:

Using SDWebImage, I managed to reduce my memory footprint by 1 order of magnitude. And it does all the asynchronous loading as well.

    NSString *imageString = [[_imgurArray objectAtIndex:indexPath.row] objectForKey:@"link"];
    [imageView setImageWithURL:[NSURL URLWithString:imageString]];
    cell.backgroundView = imageView;

Screen Shot 2014-02-05 at 12.50.19 pm

 

 

Haha! Mind blown!

Advertisements

2 thoughts on “Loading Images in UICollectionViewCell: Naive to the Clever.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s