Preloading and caching images

Oftentimes we come across situations where clients (for a number of reasons) do not deliver final images or artwork for content until right before the app is submitted — or sometimes after the app is in review or launched. While not the best scenario, we have come up with a system to account for this so it doesn’t affect our timelines.

We start with a local plist file that contains strings pointing to images on a server. This embedded file will then be duplicated (copied to the local file storage) when the app is run for the first time. Then, it will attempt to download the latest version of the plist from the internet, and will continually replace the local version if there are online updates.

The strings within the plist contain urls to images that will be used within the application. For that, we have been using the EGOImage libraries from:

What if images on the iPhone were as easy as HTML?

to download and cache the files. The great part about this is that the download happens once — once the image is downloaded, it is saved in the local file sandbox, and it will be pulled from there on subsequent views.

Here is how we have implemented this on a number of recent projects:

in AppDelegate.h

declare:


	#import "EGOImageView.h"

	#define DATA_PLIST_URL @"http://website.com/plist.xml"

	NSString *cachePath;
	NSArray *arrCachedData, *arrTemp;
	int intPreloadImage;
	EGOImageView *img;

        - (void)imageViewLoadedImage:(EGOImageView*)imageView;

in AppDelegate.m:


- (void) preLoadImages {
	if (intPreloadImage < [arrCachedData count]) {
		img = [[EGOImageView alloc] initWithPlaceholderImage:[UIImage imageNamed:@"btn_pixel.png"] delegate:self];
		img.imageURL = [NSURL URLWithString:[[arrCachedData objectAtIndex:intPreloadImage] valueForKey:@"image"]];
	}
}

- (void)imageViewLoadedImage:(EGOImageView*)imageView {
	[img release];
	intPreloadImage++;
	[self preLoadImages];
}

- (void) reloadCachedData {
	if ([arrTemp count] > 1) { // at least 2 items
		[arrTemp writeToFile:cachePath atomically:YES];
		if (arrCachedData)
			[arrCachedData release];
		arrCachedData = [[NSArray alloc] initWithContentsOfFile:cachePath];
		
		// cache images
		intPreloadImage = 0;
		[self preLoadImages];
	}
}

- (void) loadRemoteData {
	NSURL *dataURL = [NSURL URLWithString:DATA_PLIST_URL];
	
	arrTemp = [NSArray arrayWithContentsOfURL:dataURL];
	
	[self performSelectorOnMainThread:@selector(reloadCachedData) withObject:nil waitUntilDone:YES];
}

- (void) loadCache {
	NSString *path = [[NSBundle mainBundle] bundlePath];
	NSString *finalPath = [path stringByAppendingPathComponent:@"LocalData.plist"];
	
	NSFileManager *fileManager = [NSFileManager defaultManager];
	NSError *error;
	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
	NSString *documentsDirectory = [paths objectAtIndex:0];
	cachePath = [[documentsDirectory stringByAppendingPathComponent:@"LocalData.plist"] retain];
	
	BOOL fileExists = [fileManager fileExistsAtPath:cachePath];
	
	if (!fileExists) {
		fileExists = [fileManager copyItemAtPath:finalPath toPath:cachePath error:&error];
	}
	
	BOOL filecopied = [fileManager fileExistsAtPath:cachePath];
	
	if (filecopied) {
		NSLog(@"Cache plist file ready.");
	} else {
		NSLog(@"Cache plist not working.");
	}
	
	// load data from file cache
	
	arrCachedData = [[NSArray alloc] initWithContentsOfFile:cachePath];
	
	// download remote file, and update array, if available
	
	NSOperationQueue *queue = [NSOperationQueue new];
	
	NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self 
			selector:@selector(loadRemoteData) 
			object:nil];
	[queue addOperation:operation];
	[operation release];
	
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    
    // Add the view controller's view to the window and display.
    [self.window addSubview:viewController.view];
    [self.window makeKeyAndVisible];
	
    [self loadCache];

    return YES;
}


- (void)dealloc {
    if (cachePath)
        [cachePath release];
    if (arrCachedData)
        [arrCachedData release];
    [img release];
    [viewController release];
    [window release];
    [super dealloc];
}

The following is a simple example of a plist file (the urls would need to be valid links to images, but this should give you a basic idea of how to format this):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
	<dict>
		<key>title</key>
		<string>This is a title</string>
		<key>image</key>
		<string>http://www.website.com/image1.png</string>
		<key>url</key>
		<string>http://website.com/url1</string> 
	</dict>
	<dict>
		<key>title</key>
		<string>This is another title</string>
		<key>image</key>
		<string>http://www.website.com/image2.png</string>
		<key>url</key>
		<string>http://website.com/url2</string> 
	</dict>
</array>
</plist>
jeffrey

About jeffrey

Jeffrey Berthiaume is a multimedia developer and internet architect who has designed and built award-winning websites, kiosks, and content management systems. He bridges the gap between creative and technology with an ability to balance the needs of designers and marketing with the capabilities of existing technology.
This entry was posted in iOS. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>