Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loading images asynchronously, wrong image in cell

I have a UICollectionView in which I am loading multiple images into. From what Ive been reading, in order to match the correct image to each cell I need to subclass UIImageView and get the image there. Because every time I collectionView reloadData, some images duplicate and they are all out of order. But I am unsure how to do this and haven't found any tutorials. I am using Parse for a database.

 - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
albumImageCell *cell = (albumImageCell *) [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];

     if (cell == nil) {
         cell = [[albumImageCell alloc]init];
     }

     PFObject *temp = [_dataArray objectAtIndex:indexPath.row];
     PFFile *file = [temp objectForKey:@"imageThumbnail"];

     if (![cell.hasImage isEqualToString:@"YES"]) {
         dispatch_async(imageQueue, ^{
             NSData *data = [file getData];
                 if (data) {
                     UIImage *image = [UIImage imageWithData:data];

                     dispatch_async(dispatch_get_main_queue(), ^(void){
                         cell.imageView.image = image;
                         cell.hasImage = @"YES";
                     });

                 }
         });
     }

     return cell;
}
like image 869
Peter Avatar asked Jul 14 '15 23:07

Peter


2 Answers

One way to solve this is to re-query the collection view for the cell again once you're back on the main queue. This code should work:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
albumImageCell *cell = (albumImageCell *) [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];

     if (cell == nil) {
         cell = [[albumImageCell alloc]init];
     }

     PFObject *temp = [_dataArray objectAtIndex:indexPath.row];
     PFFile *file = [temp objectForKey:@"imageThumbnail"];

     if (![cell.hasImage isEqualToString:@"YES"]) {
         dispatch_async(imageQueue, ^{
             NSData *data = [file getData];
                 if (data) {
                     UIImage *image = [UIImage imageWithData:data];

                     dispatch_async(dispatch_get_main_queue(), ^(void){
                         // cellAgain will be the actual cell at that index path, if it is visible. 
                         // If it is not visible, cellAgain will be nil.
                         albumImageCell *cellAgain = [collectionView cellForItemAtIndexPath:indexPath];
                         cellAgain.imageView.image = image;
                         cellAgain.hasImage = @"YES";
                     });

                 }
         });
     }

     return cell;
}
like image 184
Zev Eisenberg Avatar answered Nov 07 '22 07:11

Zev Eisenberg


I made a small 'tutorial' in answer to a this question. Although the question refers to Core Data, my answer applies to any data source so you should be able to fit it around your use case.

One thing you want to watch out for is the inner block, when you get back onto the main queue. Given that you have no idea how long it takes to get to that point, the cell may no longer be relevant to that image (could have been reused), so you need to do a couple of additional checks...

(a) is the image still required?

if ([[tableView indexPathsForVisibleRows] containsObject:indexPath])

(b) is that cell is the correct cell for the image?

 UITableViewCell * correctCell = [self.tableView cellForRowAtIndexPath:indexPath];

Although this tutorial is still valid, I tend to abstract things further these days. As the viewController has to deal with thread-unsafe entities like UIKit and Core Data, it is a good idea to keep all viewController code on the main thread. Background queue abstractions should take place at a lower level, preferably in the model code.

like image 1
foundry Avatar answered Nov 07 '22 06:11

foundry