1

I'm using Parse to store images. I am trying to load the images asynchronously so it does not interfere with the collectionView scroll. I am new to using dispatch_async, and am not so sure how to properly implement it. I've also looked into lazy load, but I thought this would work. But it doesn't, and the collectionView scroll is choppy. Thanks

- (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:@"image"];

    if (cell.hasImage == FALSE) {
        dispatch_async(imageQueue, ^{
            NSData *data = [file getData];
                if (data) {
                    dispatch_async(dispatch_get_main_queue(), ^(void){
                        cell.imageView.image = [UIImage imageWithData:data];
                        cell.hasImage = TRUE;
                    });

                }
        });
    }

    return cell;
} 
Meet Doshi
  • 4,241
  • 10
  • 40
  • 81
Peter
  • 1,053
  • 13
  • 29
  • 1
    There are problems with this code beyond choppiness. A cell can get reused while images are being loaded in the background. You could wind up with the wrong image connected to a particular cell. I think the best solution I've seen to this issue to create an asynchronous UIImageView class that loads in the background on its own. I don't know if this particular one is good, but you might give it a try: https://github.com/nicklockwood/AsyncImageView – EricS Jul 14 '15 at 00:11
  • @EricS Yes this has happened before. Do you think i would be able to load and set the image in my custom `collectionViewCell` file instead of subclass `UIImageView`? – Peter Jul 14 '15 at 00:28
  • Sure, but you'll have to handle the case when the cell gets reused by implementing the `prepareForReuse` method. You'll want to cancel the existing network request, or at least ignore the result, and create a new one. Remember that the second request may return before the first one - there's no guaranty they'll come back in any particular order. – EricS Jul 14 '15 at 17:02

1 Answers1

1

Because UIImage +imageWithData method blocks the main thread a bit.

dispatch_async(dispatch_get_main_queue(), ^(void){
    cell.imageView.image = [UIImage imageWithData:data];

So these lines should be as the following.

UIImage *image = [UIImage imageWithData:data];

dispatch_async(dispatch_get_main_queue(), ^(void){
    cell.imageView.image = image;

Besides, UIImage doesn't decode the image immediately till the UIImage object is actually shown or rendered. See https://stackoverflow.com/a/19251240/629118. Thus, the following code is one of the best way to get rid of choppy scrolling.

UIImage *image = [UIImage imageWithData:data];

UIGraphicsBeginImageContext(CGSizeMake(1,1));
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), [image CGImage]);
UIGraphicsEndImageContext();

dispatch_async(dispatch_get_main_queue(), ^(void){
    cell.imageView.image = image;
Community
  • 1
  • 1
Kazuki Sakamoto
  • 13,929
  • 2
  • 34
  • 96
  • Our solution to this problem was to create a CGImageRef on the background thread instead of a UIImage and pass the CGImageRef to the main thread. The main thread would create a UIImage from the CGImageRef, which was pretty fast. (UIImage also wasn't thread-safe in earlier versions of iOS, so we got in the habit of never using them on secondary threads) – EricS Jul 14 '15 at 00:16