Images in UITableView keep re-loading and wrong im

2019-02-21 01:00发布

问题:

i have created a UITableView which populates each cell from URL requests. I have used 'dispatch_queue' to prevent the UItableView from freezing. For some reason when i scroll through the UITableView the images flash and disappear and fill the wrong cell for a second until fixing themselves. Here is my code. I am pulling the feed using restkit

customCell.customCellTextLabel.text = [NSString stringWithFormat:@"%@",feedO.title];

    NSString *string = [NSString stringWithFormat:@"%@",feedO.body];

    NSURL *urlString;

        NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
        NSArray *matches = [linkDetector matchesInString:string options:0 range:NSMakeRange(0, [string length])];
        for (NSTextCheckingResult *match in matches) {
            if ([match resultType] == NSTextCheckingTypeLink) {
                urlString = [match URL];
                NSLog(@"found Body URL: %@ and title %@", urlString,feedO.title);
            }
        }

        dispatch_queue_t imageQueue = dispatch_queue_create("imageDownloader",nil);
        dispatch_async(imageQueue, ^{

            NSData *data = [[NSData alloc] initWithContentsOfURL:urlString];

            dispatch_async(dispatch_get_main_queue(), ^{

                customCell.customCellImageView.image = [UIImage imageWithData: data];
            });
        });


    return customCell;

回答1:

You should consider using this library SDWebImage, available here https://github.com/rs/SDWebImage for that kind of problems. It handles asynchronous download and cache for remote images very easily.

The simpliest installation is done by using CocoaPods

CocoaPods (http://cocoapods.org) is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries in your projects. See the Get Started section for more details.

Use in your Podfile

platform :ios, '6.1'
pod 'SDWebImage', '~>3.6'

After installing the dependency for SDWebImage, just simply use the following lines in your view controller :

#import <SDWebImage/UIImageView+WebCache.h>

...

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

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];

    if (cell == nil)
    {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                       reuseIdentifier:MyIdentifier] autorelease];
    }

    // Here we use the new provided setImageWithURL: method to load the web image
    [cell.imageView setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
                   placeholderImage:[UIImage imageNamed:@"placeholder.png"]];

    cell.textLabel.text = @"My Text";
    return cell;
}


回答2:

your code was missing one line , that i added.

customCell.customCellImageView.image =nil;

customCell.customCellTextLabel.text = [NSString stringWithFormat:@"%@",feedO.title];

NSString *string = [NSString stringWithFormat:@"%@",feedO.body];

NSURL *urlString;
customCell.customCellImageView.image =nil;

    NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
    NSArray *matches = [linkDetector matchesInString:string options:0 range:NSMakeRange(0, [string length])];
    for (NSTextCheckingResult *match in matches) {
        if ([match resultType] == NSTextCheckingTypeLink) {
            urlString = [match URL];
            NSLog(@"found Body URL: %@ and title %@", urlString,feedO.title);
        }
    }

    dispatch_queue_t imageQueue = dispatch_queue_create("imageDownloader",nil);
    dispatch_async(imageQueue, ^{

        NSData *data = [[NSData alloc] initWithContentsOfURL:urlString];

        dispatch_async(dispatch_get_main_queue(), ^{

            customCell.customCellImageView.image = [UIImage imageWithData: data];
        });
    });


return customCell;


回答3:

The reason for this is the UITableView is reusing the cells created. So firstly it creates lets say 10 of your CustomCell class.

Cell 0
Cell 1
...
Cell 9

By creating these 10 cells it will initiate 10 async calls to fetch images.

block 0 -> Cell 0
block 1 -> Cell 1
...
block 9 -> Cell 9

This will work fine if you don't scroll until all 10 downloads have finished.

But as soon as you start scrolling, the UITableView will start reusing the cells created and initiate new downloads.

So first it might reuse Cell 0 and create block 10 -> Cell 0.

If block 0 was not finished when Cell 0 was picked for reuse, you will now have two blocks wanting to put their image onto the same cell. Leading to the following two scenarios:

  1. Block 0 finishes first, then block 10
  2. Block 10 finishes first, then block 0

This is what is causing the "flashing".

Then imagine scrolling through 1000s of cells within seconds :)

Solution

You need to be able to cancel the queued block for your cell being reused.

I would use e.g. SDWebImage or FastImageCache.



回答4:

How you said. You catch the image in another thread, in order to prevent freezing. And in a tableView the cells are reused, and while in one background thread you are catching the new photo, in the main thread the cell with the previous photo is returned. Here in the code I fix it and better explanation:

customCell.customCellTextLabel.text = [NSString stringWithFormat:@"%@",feedO.title];

NSString *string = [NSString stringWithFormat:@"%@",feedO.body];

NSURL *urlString;

    NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
    NSArray *matches = [linkDetector matchesInString:string options:0 range:NSMakeRange(0, [string length])];
    for (NSTextCheckingResult *match in matches) {
        if ([match resultType] == NSTextCheckingTypeLink) {
            urlString = [match URL];
            NSLog(@"found Body URL: %@ and title %@", urlString,feedO.title);
        }
    }
   // Here you need, at least quit the previous imagen, is better if you implement
   //   and activity indicator (here start), and also you should implement some imagen cache.
   customCell.customCellImageView.image = nil;


   // Apart from here, go to another thread.

    dispatch_queue_t imageQueue = dispatch_queue_create("imageDownloader",nil);
    dispatch_async(imageQueue, ^{

        // this operation is slowly that return a cell, this happens after [1]
        NSData *data = [[NSData alloc] initWithContentsOfURL:urlString];

        dispatch_async(dispatch_get_main_queue(), ^{

        //If you implement and activity indicator stop here.
            customCell.customCellImageView.image = [UIImage imageWithData: data];
        });
    });

// [1] the cell is returned before the image is ready.
return customCell;


回答5:

Above answer is correct. But no need to download image in cell for row. you can download image before table loading occurs in viewWillAppear or ViewDidLoad which fits to your requirement.



回答6:

When you scroll your table every time your image will download again. to prevent this i prefer you to get image from cache and set in your imageview directly. so as per your code. if you scroll from 0 index to 10 then cell will download image for all 10 cells. after that if you scroll again 10 to index 0 . then image will be download again.

you can use (https://github.com/rs/SDWebImage) to download image async. its very easy and fast.

i prefered this library because it will handle your cache. just write below code

#import "UIImageView+WebCache.h"

// in cellForRowAtIndexPath.
[customCell.customCellImageView sd_setImageWithURL: [NSURL URLWithString:urlString]];

remove below code.

dispatch_queue_t imageQueue = dispatch_queue_create("imageDownloader",nil);
    dispatch_async(imageQueue, ^{

        NSData *data = [[NSData alloc] initWithContentsOfURL:urlString];

        dispatch_async(dispatch_get_main_queue(), ^{

            customCell.customCellImageView.image = [UIImage imageWithData: data];
        });
    });

Maybe this will help you.