Asynchronously set images in tableview

2019-01-29 09:52发布

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!

4条回答
欢心
2楼-- · 2019-01-29 10:29

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

查看更多
走好不送
3楼-- · 2019-01-29 10:31

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
   }


}];
查看更多
老娘就宠你
4楼-- · 2019-01-29 10:47
  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.

查看更多
做个烂人
5楼-- · 2019-01-29 10:52

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.

查看更多
登录 后发表回答