1

I'm currently using SDWebImage to load pictures for my table cells, using the following code:

[cell.coverImage sd_setImageWithURL:[self.dataInJSONModel.Content[indexPath.row] CoverImage] placeholderImage:[UIImage imageNamed:@"imageplaceholder_general"]];

The problem is when I scroll up and down, the images were inserted into the wrong cells. After reading some post on StackOverflow regarding this issue, I suspect it to be due to that cells are reused when we scroll and hence the asynchonous download of the image may be placed on a cell indexPath that has changed.

Hence I implemented several changes e.g.:

SDWebImageManager *manager = [SDWebImageManager sharedManager];
UIImageView * cellCoverImage = cell.coverImage;
[manager downloadImageWithURL:[self.dataInJSONModel.Content[indexPath.row] CoverImage] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL * oriURL) {

      NSArray *visibleIndexPaths = [tableView indexPathsForVisibleRows];
      if ([visibleIndexPaths containsObject:indexPath]) {

          cellCoverImage.image = image;
       }
   }]; 

Or even to compare URLs:

SDWebImageManager *manager = [SDWebImageManager sharedManager];
UIImageView * cellCoverImage = cell.coverImage;
[manager downloadImageWithURL:[self.dataInJSONModel.Content[indexPath.row] CoverImage] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL * oriURL) {

   if([oriURL isEqual:[self.dataInJSONModel.Content[indexPath.row] CoverImage]])
    {
        cell.coverImage.image = image;

    }

 }];

Still the problem persist. Or I might have wrongly programmed it? Found several suggestions online but no concrete solution yet seen.

Need help!

EDIT

I've already made some changes to it but still doesn't work:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

NewsFeedCell * cell = [tableView dequeueReusableCellWithIdentifier:@"NewsFeedCell" forIndexPath:indexPath];


if (self.dataInJSONModel)
{
   cell.coverImage.image = nil;
    SDWebImageManager *manager = [SDWebImageManager sharedManager];
    [manager downloadImageWithURL:[self.dataInJSONModel.Content[indexPath.row] CoverImage] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL * oriURL) {

        if ([cell isEqual:[self.tableView cellForRowAtIndexPath:indexPath]])
        {
            cell.coverImage.image = image;
        }

    }];

}
Marcus
  • 548
  • 1
  • 5
  • 13

5 Answers5

5

I met the same problem, and tried assign .image = nil, but not work.

Finally, my sloution is to override prepareForReuse in UITableViewCell with cancel operation:

- (void)prepareForReuse  
{
    [super prepareForReuse];
    [_imageView sd_cancelCurrentImageLoad];
}
KevinChang
  • 51
  • 1
  • 2
2

Posted the question on the SDWebImage Github page and gotten a suggestion from someone who solves my problem! I just override the prepareForReuse method in my cell's implementation file and nullify the image of the affected imageView.

Sample code for future reader:

In my NewsFeedCell.m
- (void) prepareForReuse
{
    [super prepareForReuse];

    self.coverImage.image = NULL;

}

And this solves the problem! My opened issue at GitHub is https://github.com/rs/SDWebImage/issues/1024, should any of you want to see.

Marcus
  • 548
  • 1
  • 5
  • 13
1

I tried most of these solutions, spent some time fixing this. I got 2 solutions working for me.

  • When setting image to ImageView in cells stop download.

In cellForRow add this:

cell.imageView.sd_cancelCurrentImageLoad()

and then in cell:

func prepareForReuse() {
     imageView.image = UIImage.placeholderImage() // or nill
}

This is not real solutions because your actually stop image download and waste already downloaded data.

  • Another more elegant solutions is adding extension for UIImageView:

Choose animation which suits you, and try this:

func setImageAnimated(imageUrl:URL, placeholderImage:UIImage) {
    self.sd_setImage(with: imageUrl, placeholderImage: placeholderImage , options:SDWebImageOptions.avoidAutoSetImage, completed: { (image, error, cacheType, url) in
        if cacheType == SDImageCacheType.none {
            UIView.transition(with: self.superview!, duration: 0.2, options:  [.transitionCrossDissolve ,.allowUserInteraction, .curveEaseIn], animations: {
                self.image = image
            }, completion: { (completed) in
            })
        } else {
            self.image = image
        }
    })
}
Dejan Zuza
  • 359
  • 1
  • 6
  • 14
0

You are right in your analysis of the problem, just not executed it quite correctly.

some pseudocode for cellForRowAtIndexPath...

 - set the cell.imageView.image to nil to wipe out 
                previous use of cell image view contents
 - get the URL from data source
 - initiate asynchronous download with completion ^{
      //check the cell is still the correct cell
      if ([cell isEqual: [collectionView cellForRowAtIndexPath:indexPath]]) {
            cell.imageView.image = image
      }
 }

A couple of things you are doing wrong
- don't grab a reference to the cell's image view until you know you need it (in the completion block)
- don't check visibleIndexPaths, the indexPath might still be visible but allocated to a different cell (if you have scrolled off then on again for example). The 'cell isEqual' technique I use here should suffice for all cases.

You can also nil out old cell contents for a recycled cell by overriding the cells -prepareForReuse method.

foundry
  • 31,615
  • 9
  • 90
  • 125
0

The Obvious error here is that you're not accounting for using Blocks. Essentially, the completion happens on a background thread, and all UI updates must happen on the Main Thread.

The simple solution is;

   dispatch_async(dispatch_get_main_queue(), ^{
      cell.coverImage.image = image;
   });

Further, if you intend to reference the tableView in your completion block, you should use a weak reference to it.

MDB983
  • 2,444
  • 17
  • 20