Thursday, February 14, 2013

Load Image Asynchronously in iOS

This post describes how to load an image from a remote server asynchronously in iOS, and from this post it even shows how to subclass UIImageView.

Whenever our application communicates with a server for data we usually use asynchronous calls to server from client side,so as to avoid blocking or so as to maintain thread safety, suppose say we are fetching an image from a url since we are making an asynchronous call from client side and since it is asynchronous call we can't make sure that when exactly we will get response so till that time we need to show some default image to user and once you get the actual image refresh the UIImageView with this image.

Here LoadImageAsynchronousSample demo app available to download

Algorithm:
Step 1: Subclass UIImageView and add a method to this class to load image asynchronously.
Step 2: Add a placeholder default image to this imageview initially, until you get actual image from service(url).

Step 3: Create an object of NSURLRequest so as to invoke asynchronous service call and implement NSURLConnection Methods delegate methods so as to handle response.
Step 4: Once you get response successfully from service just load that image(data) to your imageview.

LoadImageAsynchronously.h file is as below,

#import <UIKit/UIKit.h>
@interface LoadImageAsynchronously : UIImageView{
    NSURLConnection *connection;
    NSMutableData *data;
}
- (void)loadImageAsyncFromURL:(NSURL *)url placeholderImage:(UIImage *)placeholderImg; //method to load image asynchronously
@end

loadImageAsyncFromURL method you need to call from your class file where ever you want to load an image from a url, this method accepts 2 paramaters one is your image url from where you want to load another is placeholderImg which is to fill your image view with some local default image from your project bundle initially until and unless you get the actual image from your image url.

LoadImageAsynchronously.m file is as below,


#import "LoadImageAsynchronously.h"
@implementation LoadImageAsynchronously

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
    }
    return self;
}

#pragma mark - LoadImageAsyncFromURL Method
- (void)loadImageAsyncFromURL:(NSURL *)url placeholderImage:(UIImage *)placeholderImg{
    if(placeholderImg)
    {
        self.image=placeholderImg; //placeholderImg is a local image inside project bundle

    }
    
    NSURLRequest *request=[[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:90.0];
    if(connection)
    {
        [connection cancel];
        connection=nil;
        data=nil;
    }
    connection=[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];

}

#pragma mark - NSURLConnection Methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
     [data setLength:0];
}

- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)incrementalData 
{
    if (data == nil)
        data = [[NSMutableData alloc] init];
    [data appendData:incrementalData];
    
}

- (void)connectionDidFinishLoading:(NSURLConnection *)theConnection
{
    UIImage *image=[UIImage imageWithData:data]; //image data from service(url)
    if(image){
        self.image=image;
    }
    data=nil;//so as to flush any cache data
    connection=nil;//so as to flush any cache data
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
      NSLog(@"Connection failed: %@", [error description]);
}

and finally in your calling class just import LoadImageAsynchronously.h file and create an object of LoadImageAsynchronously class as below,

#import <UIKit/UIKit.h>
#import "LoadImageAsynchronously.h"
@interface ViewController : UIViewController
{
    LoadImageAsynchronously *myImageView;
}
@end

finally pass your image url and call loadImageAsyncFromURL method of LoadImageAsynchronously class as below,


#import "ViewController.h"

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSString *imageURL = @"http://www.gstatic.com/webp/gallery/1.jpg";//image url
    UIImage *defaultPlaceholderImg = [UIImage imageNamed:@"default_image.jpeg"];//from local bundle
    myImageView=[[LoadImageAsynchronously alloc] initWithFrame:CGRectMake(20.0, 20.0, 200.0, 200.0)];
    myImageView.contentMode=UIViewContentModeScaleAspectFill;
    myImageView.clipsToBounds=true;
    NSURL *url = [NSURL URLWithString:imageURL];
    [myImageView loadImageAsyncFromURL:url placeholderImage:defaultPlaceholderImg];  //UIImageView subclass method
    [self.view addSubview:myImageView];
}

@end

and thats it, hope you enjoyed the post, any pros or cons or suggestions is appreciated and accepted in advance, thank you,,,, :-)