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,,,, :-)




4 comments:

  1. it is getting one image asynchronously at a time, can u please help me to get bulk of images from array of url string asynchronously as UIImage.

    Thanks in advance.

    ReplyDelete
    Replies
    1. You can use the same methods and the same class which I have mentioned in this post even for the bulk of images from the array, Create NSURLRequest objects as per your array count and assign a unique tag for every request object so that whenever the asynchronous response comes you can differentiate the response easily. Hope this helps,, :)

      Delete
  2. When i use you code in tableview cell it displace images but when i scroll table it delete the previous images which was already displayed

    ReplyDelete
    Replies
    1. UITableView only loads and keeps the visible frame cell data in memory at any instance of time, So when ever you try to scroll the tableview make sure that how you will manage cell data in tableViews cellForRowAtIndexPath datasource method,
      You should fetch the image for the cell for a particular indexPath.row in cellForRowAtIndexPath.
      Since when you scroll down your previous cell data will not be available in memory you are losing the data when you scroll back upwards, Its our responsibility to refetch/refresh the respective visible cells(indexpath.row) data again when we scroll back in cellForRowAtIndexPath.
      Hope this helps,,, :)

      Delete