简体   繁体   中英

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!

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];
               }
           }); 
        }
    });
}

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

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"]]; 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM