user1282637
user1282637

Reputation: 1867

Asynchronously set images in tableview

I have a TableView using custom cells. I initially was setting grabbing an image from a URL in the cellForRowAtIndexPath method

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *simpleTableIdentifier = @"SimpleTableCell";

    SimpleTableCell *cell = (SimpleTableCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
    if (cell == nil)
    {
        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"SimpleTableCell" owner:self options:nil];
        cell = [nib objectAtIndex:0];
    }

    NSDictionary *dictObject = [places objectAtIndex:indexPath.row];
    cell.nameLabel.text  = [dictObject valueForKey:@"PlaceTitle"];

    NSURL *url = [NSURL URLWithString:@"http://images1.fanpop.com/images/image_uploads/Mario-Kart-Wii-Items-mario-kart-1116309_600_600.jpg"];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];
    cell.thumbnailImageView.image = image;

    return cell;
}

but this was making my TableView scroll laggy. Once I removed the image fetch, it scrolled fine, so I know this is my problem.

My question is: how can I asynchronously fetch this image and set it in my cell? Is there an easy way to do this? Thanks!

Upvotes: 0

Views: 274

Answers (4)

Mohamed Jaleel Nazir
Mohamed Jaleel Nazir

Reputation: 5821

  1. Create UIImageView Class File (i named it to MJTableImageView).

in MJTableImageView.h File

    @interface MJTableImageView : UIImageView< NSURLConnectionDelegate, NSURLConnectionDataDelegate >

    {
    NSMutableData *imageData ;
    long long expectedLength;
    NSURLConnection *currentConnection;
    NSString *File_name;

    }
    @property(nonatomic,readonly)UIActivityIndicatorView *loadingIndicator;
    @property(nonatomic)BOOL showLoadingIndicatorWhileLoading;

    -(void)setImageUrl:(NSURL *)imageUrl fileName:(NSString *)name;

    @end

in MJTableImageView.m File

-(void)setImageUrl:(NSURL *)imageUrl fileName:(NSString *)name
{
    // discard the previous connection
    if(currentConnection)
    {
        [currentConnection cancel];
    }

    File_name = name;

    //reset current image
    self.image = nil;


//    if(_showLoadingIndicatorWhileLoading)
//    {
        //show the loading indicator

        if(!_loadingIndicator)
        {
            CGFloat width = self.bounds.size.width*0.5;

            _loadingIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake((self.bounds.size.width-width)/2, (self.bounds.size.height-width)/2, 25.0 , 25.0)];
            _loadingIndicator.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.5];
            _loadingIndicator.layer.cornerRadius = width*0.1;
        }
        [self startLoadingIndicator];
//    }

    // initialize the placeholder data
    imageData = [NSMutableData data];


    // start the connection
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:imageUrl];
    request.cachePolicy = NSURLRequestUseProtocolCachePolicy;

    currentConnection = [NSURLConnection connectionWithRequest:request delegate:self];



}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    //if the image view is reused in a table view for example to load another image  previous image is discarded
    if(connection != currentConnection)
    {
        [connection cancel];
        [self cleanUp];
        return;
    }

    // append new Data
    [imageData appendData:data];



    // show the partially loaded image
    self.image = [UIImage imageWithData:imageData];
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    expectedLength = response.expectedContentLength;
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    // clean up
    [self cleanUp];

}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    // show the full image
    self.image = [UIImage imageWithData:imageData];

    NSString *filename = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/%@", File_name];
    NSData *data = UIImagePNGRepresentation([UIImage imageWithData:imageData]);
    [data writeToFile:filename atomically:YES];

    // clean up
    [self cleanUp];
}
-(void)cleanUp
{
    // clean up
    imageData = nil;
    [self stopLoadingIndicator];
}
-(void)startLoadingIndicator
{
    if(!_loadingIndicator.superview)
    {
        [self addSubview:_loadingIndicator];
    }
    [_loadingIndicator startAnimating];
}
-(void)stopLoadingIndicator
{
    if(_loadingIndicator.superview)
    {
        [_loadingIndicator removeFromSuperview];
    }
    [_loadingIndicator stopAnimating];
}

I am using StoryBoard so i add ImageClass(MJTableImageView) file to UItableviewcell ImageView and set tag number to it.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    NSDictionary *dict = [self.arr objectAtIndex:indexPath.row];

    UITableViewCell *cell = [self.MJTableView dequeueReusableCellWithIdentifier:@"MJImageCell"];
    if(cell == nil)
    {

    }
    UILabel *appName = (UILabel*)[cell.contentView viewWithTag:2];
    appName.text = [dict valueForKey:@"trackName"];

    MJTableImageView *imageview = (MJTableImageView *)[cell.contentView viewWithTag:1];

    NSString *url = [dict valueForKey:@"artworkUrl60"];

    NSString *filename = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/%@",[dict valueForKey:@"trackName"] ];
    NSData *data = [NSData dataWithContentsOfFile:filename];
    if(data)
    {
        imageview.image = [UIImage imageWithData:data];
    }
    else
    {
        [imageview setImageUrl:[NSURL URLWithString:url] fileName:[dict valueForKey:@"trackName"]];
    }
    return  cell;
}

For More details see Github Project MJTableImageSwift it is in Swift.

Upvotes: 0

MetaSnarf
MetaSnarf

Reputation: 6187

Use this code inside your tableviews cellforindexpath

 NSURLRequest *req =[[NSURLRequest alloc]initWithURL:[NSURL URLWithString:@"yourimageurl.com"]];

[NSURLConnection sendAsynchronousRequest:req queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
    if(!error){
        UIImage *image =[UIImage imageWithData:data];
        cell.thumbnailImageView.image = image;
    }
     else{
            //error
   }


}];

Upvotes: 0

jiazhh
jiazhh

Reputation: 11

dataWithContentsOfURL is a synchronous method rather than asynchronous,as Apple Documents described.

This method is ideal for converting data:// URLs to NSData objects, and can also be used for reading short files synchronously. If you need to read potentially large files, use inputStreamWithURL: to open a stream, then read the file a piece at a time.

In order to asynchronously load image,especially in tableViewCell,try use 3rd part Library SDWebImage

Upvotes: 0

gnasher729
gnasher729

Reputation: 52538

Step 1: Have a cache containing images. Either just in memory, better on disk.

Step 2: When you need an image, call a method which either returns an image from the cache, or returns a default image and starts a download.

Step 3: When a download finishes, add the image to the cache. Then find out which rows need the image. Reload all the rows that reload the image.

The download should be done asynchronously using GCD. I would really recommend that you add the download code into a separate, reusable method so that you can handle download errors. Even if you don't do it now, you will do it later.

Upvotes: 1

Related Questions