Ignacio Oroná
Ignacio Oroná

Reputation: 4399

UICollectionView cell image error

I am developing an app with a sample UICollectionView that should load a bunch of icons that it downloads from the internet. So basically the server retrieves a list with the URLs of each icon inside a JSON file, the app parses it, and then each cell downloads the corresponding image.

The problem with this approach seems to be that if the user starts scrolling while the images are downloading, the user will start seeing the images in the wrong order! It's like UICollectionView is trying to 'help me' and it renders the content it's got in the wrong place!

I've seen many threads about this, I tried out most of their suggestions but with out much luck so far. Anybody seen something like this?

enter image description here

This is how the cellForItemAtIndexPath looks like:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
PeqImage *image = [self.responseIcons objectAtIndex:indexPath.row];

PeqCustomCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:@"PeqCustomCell" forIndexPath:indexPath];

cell.peqImage = image;
cell.imageName = [NSString stringWithFormat:@"%@.png",image.assetId];

NSString *theImageUrl = [NSString stringWithFormat:@"http://www.XXXX.com/pb/images/uncompressed/%ld.png",(long)indexPath.row];
[cell downloadImage:[NSURL URLWithString:theImageUrl]];

return cell;

}

And this is how the download algorithm looks like (using UIImageView+AFNetworking):

- (void) downloadImageWithUrl:(NSURL*) url completion:(PeqCustomCellCompletionBlock)completionBlock
{UIImage *placeholder = [UIImage imageNamed:@"placeholder"];
NSURLRequest *jpegURLRequest = [NSURLRequest requestWithURL:url];

self.imageUrl = url.absoluteString;    
[self.imageView setImageWithURLRequest:jpegURLRequest
                  placeholderImage:placeholder
                  success:^(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image) {
                  image = [self normalizeImage:image];
                  completionBlock(image);
                  }

                  failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {

                      NSLog(@"Error downloading image from network");
                  }];

}

Upvotes: 0

Views: 293

Answers (4)

charmingToad
charmingToad

Reputation: 1597

The problem is that collection view cells are reused. So you're' telling a cell to download a particular image while its onscreen, and then it goes offscreen and gets reused when the collection view needs another cell, and then you're telling the SAME cell to download another image. Whichever image finishes downloading last is the last one that will be displayed.

In your download completion block, before setting image = [self normalizeImage:image];, you should check if the request url matches the current self.imageUrl. If it doesn't, then the image is from an old request and you don't want to display it in the cell.

Upvotes: 1

Ketan Parmar
Ketan Parmar

Reputation: 27448

You should use SDWebImage library to handle this kind of stuff something like,

    #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 sd_setImageWithURL: method to load the web image
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
                  placeholderImage:[UIImage imageNamed:@"placeholder.png"]];

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

here is the example of tableview you can use same for your collection view!!

this library cache the images for reusability.

If you want to implement or know native way of this stuff then you can refer Loading Images in UICollectionViewCell: Naive to the Clever.

Upvotes: 1

TyR
TyR

Reputation: 712

I suspect threads as being the culprit. All UI code must be called from the main thread. These days with so many blocks in use, it's easy to accidentally make UI calls from a background thread. When you do this, all manner of weirdness begins to materialize.

I'd try changing your last snippet of code to this, so that the image calls get dispatched on the main thread. I've just added a dispatch_async() function.

self.imageUrl = url.absoluteString;
[self.imageView setImageWithURLRequest:jpegURLRequest
                      placeholderImage:placeholder
                               success:^(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image){
                                   dispatch_async(dispatch_get_main_queue(), ^{
                                   image = [self normalizeImage:image];
                                   completionBlock(image);
                                   });
                               }
                               failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {
                                   NSLog(@"Error downloading image from network");
                               }];

Upvotes: 1

mginn
mginn

Reputation: 16124

The issue is that you re-download the image every time the cell is hidden and shown again. The better method is to download all the data in advance and then populate the collectionView using that.

Upvotes: 0

Related Questions