Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITableViewCell load images and reused cells

i need to load from web/files some UIImages. I was searching and i found in other question this code:

    if (![[NSFileManager defaultManager] fileExistsAtPath:user.image]) {
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,  0ul);
        dispatch_async(queue, ^{
            NSData *imageData =[NSData dataWithContentsOfURL:[NSURL URLWithString:user.imageURL]];

            [imageData writeToFile:user.image atomically:YES];
            dispatch_sync(dispatch_get_main_queue(), ^{
                UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
                UIImage *image = [UIImage imageWithData:imageData];
                [self.imageFriends setObject:image forKey:[NSNumber numberWithInt:user.userId]];
                cell.imageView.image = image;
                [cell setNeedsLayout];
                NSLog(@"Download %@",user.image);
            });
        });
        cell.imageView.image=[UIImage imageNamed:@"xger86x.jpg"];
    } else {
        NSLog(@"cache");
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,  0ul);
        dispatch_async(queue, ^{
            UIImage *image = [UIImage imageWithContentsOfFile:user.image];
            //[self.imageFriends setObject:image forKey:[NSNumber numberWithInt:user.userId]];
            dispatch_sync(dispatch_get_main_queue(), ^{
                UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
                newCell.imageView.image=image;
                [newCell setNeedsLayout];
            });
        });
    }

But the problem is that when i scroll fast to bottom or top the images are loaded wrong and there is a short lag when it ends.

So the question is.. how can i load the UIImages in the correct cell when i use queues to fetch them? Thanks!

like image 376
Gerardo Avatar asked Feb 08 '12 02:02

Gerardo


2 Answers

I suspect the incorrect images you see are a result of you not setting your place holder image in the event of you having a local copy of an image but still retrieving the local copy asynchronously. Also In the code you appended for loading the local copy you use UIImage a UIKit component on a background thread.

Also interestingly you seem to be doing some kind of UIImage caching. Adding the images to what I assume is an NSMutableArray property named imageFriends. But you seem to have commented out the cache add in the event you have a local copy of the file. Also your posted code never uses the cached UIImages.

While 2 levels of caching seems a bit overboard if you wanted to do this you could do something like this:

UIImage *userImage = [self.imageFriends objectForKey:[NSNumber numberWithInt:user.userId]];
if (userImage) { // if the dictionary of images has it just display it
    cell.imageView.image = userImage;
}
else {
    cell.imageView.image = [UIImage imageNamed:@"xger86x.jpg"]; // set placeholder image
    BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:user.image];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSData *imageData = nil;
        if (fileExists){
            imageData = [NSData dataWithContentsOfFile:user.image];
        }
        else {
            imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:user.imageURL]];
            [imageData writeToFile:user.image atomically:YES];
        }
        if (imageData){
           dispatch_async(dispatch_get_main_queue(), ^{ 
                    // UIKit, which includes UIImage warns about not being thread safe
                    // So we switch to main thread to instantiate image
               UIImage *image = [UIImage imageWithData:imageData];
               [self.imageFriends setObject:image forKey:[NSNumber numberWithInt:user.userId]];
               UITableViewCell *lookedUpCell = [tableView cellForRowAtIndexPath:indexPath];
               if (lookedUpCell){
                   lookedUpCell.imageView.image = image;
                   [lookedUpCell setNeedsLayout];
               }
           }); 
        }
    });
}

UIImages are part of UIKit and not thread safe. But you can load the NSData on another thread.

like image 110
NJones Avatar answered Nov 11 '22 16:11

NJones


You are loading image asynchronously, so during fast scrolling cells get reused faster then images are downloaded. One way to avoid loading a wrong image, would be to check if cell already got reused when image is loaded. Or cancel all requests in progress when you dequeue new cells.

I would also recommend to look at AFNetworking, as it contains helpful category for UIImageView, so you can do something like this:

[imageView setImageWithURL:[NSURL URLWithString:@"http://i.imgur.com/r4uwx.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder-avatar"]];

It also contains cancelImageRequestOperation method, to cancel requests in progress. Then your code will look like this:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
} else {
    [cell.imageView cancelImageRequestOperation];
}
[cell.imageView setImageWithURL:[NSURL URLWithString:user.imageURL] placeholderImage:[UIImage imageNamed:@"xger86x.jpg"]]; 
like image 24
Andrew Kurinnyi Avatar answered Nov 11 '22 17:11

Andrew Kurinnyi