The UIImageView in the iOS SDK is a great class that allows developers to quickly and easily handle the display of an image. The UIImageView is simple a container view with a single UIImage displayed within it. The nice part is that it handles all the display and sizing of the UIImage, so that developers don’t need to spend a long time playing around with ratios to get the sizing that they require.
We can initialise the UIImageView directly with an image, and this image supports using NSData to initialise it. It would seem we could simple get NSData from an NSURL, and bingo, we could load images easily from remote addresses. However, as you will find out – although this works, it essentially blocks the UI display thread until the image has loaded. This means that the display does not update until the image has been fetched from the web, and will present the user with what seems like an unusable pause.
To get round this we can implement lazy loading; where the request to retrieve the image is fired off asynchronously, and allows the UI display thread to continue till the image is received. Apple has an example in their dev knowledge base that allows you to do this directly on models. In this article we are going to look at doing this directly on a UIImageView.
In order to do this, we first create a header file for a class that inherits from UIImageView. We can call this UILazyImageView. It will, at first, look like the below:
#import <Foundation/Foundation.h> @interface UILazyImageView : UIImageView { } @end
In order to implement the delegate for NSURLConnection, which we will use to do the asynchronous fetch of the image (as in last week’s post on creating an asynchronous XML parser for iOS applications); we will need a NSMutableData variable to store the data retrieved from the URL. We use NSMutableData rather than NSData, because we can append to it since it is a mutable data type.
We will also need a new initialisation method, to initialise with a URL; and this will call off to a method to load from a URL.
Having input these the header file will now look like this:
#import <Foundation/Foundation.h> @interface UILazyImageView : UIImageView { NSMutableData *receivedData; } - (id)initWithURL:(NSURL *)url; - (void)loadWithURL:(NSURL *)url; @end
Next, in our code (.m) file for UILazyImageView we can add the init method, which calls the load method, as follows:
- (id)initWithURL:(NSURL *)url { self = [self init]; if (self) { receivedData = [[NSMutableData alloc] init]; [self loadWithURL:url]; } return self; }
Next, we need to implement the loadWithURL method. This needs to fire of an asynchronous connection; the work of dealing with the image will only happen once the connection has completed.
This will look like:
- (void)loadWithURL:(NSURL *)url { NSURLConnection *connection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:url]delegate:self]; [connection start]; }
We now need to implement the delegate for the connection (which has been set to the UILazyImageView. For this we need to at least implement didReceiveResponse, didReceiveData and connectionDidFinishLoading.
We will implement didReceiveResponse to clear the data (as this may be called when 302 redirects occur); and didReceiveData to append to the receivedData item.
Finally in connectionDidFinishLoading we can display the image, from the receivedData.
This will look like:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { [receivedData setLength:0]; } -(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [receivedData appendData:data]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { self.image = [[UIImage alloc] initWithData:receivedData]; }
If we run this, we will notice it works as planned, but the image seems to appear very suddenly. We can make this a more gentle introduction, by setting the alpha to 0 before the connection begins, and then animating it to 1 (effectively creating a fade in effect).
We would do this with code like:
- (void)loadWithURL:(NSURL *)url { self.alpha = 0; NSURLConnection *connection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:url]delegate:self]; [connection start]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { self.image = [[UIImage alloc] initWithData:receivedData]; [UIView beginAnimations:@"fadeIn" context:NULL]; [UIView setAnimationDuration:0.5]; self.alpha = 1.0; [UIView commitAnimations]; }