UICollectionView - Downloading images and effective reloading of data, like Instagram

10,710

Solution 1

The technique you want to implement is called lazy loading. Since you are using AFNetworking it will be easier to implement this in your case. Each of your collection view cell needs to have a UIImageView to display the image. Use the UIImageView+AFNetworking.h category and set the correct image URL by calling method

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    // ....

    [cell.imageView setImageWithURL:imageURL placeholderImage:[UIImage imageNamed:@"placeholder.png"]];

    // ...
    return cell;
}

Placeholder is the image which will be displayed until required image is downloaded. This will simply do the required job for you.

Note: By default, URL requests have a cache policy of NSURLCacheStorageAllowed and a timeout interval of 30 seconds, and are set not handle cookies. To configure URL requests differently, use setImageWithURLRequest:placeholderImage:success:failure:.

Also, for you reference, if you want to implement lazy loading of images yourself, follow this Apple sample code. This is for UITableView but same technique can be used for UICollectionView as well.

Hope that helps!

Solution 2

use

[self.collectionView reloadItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];

instead of

[self.collectionView reloadData];

here indexPath is the NSIndexPath Object for the corresponding UICollectionViewCell Object

Solution 3

Use following to load image Async in cell in cellForItemAtIndexPath delegate method

let url = URL(string: productModel.productImage)

    let data = try? Data(contentsOf: url!)
    DispatchQueue.main.async(execute: {
        collectionViewCell.imageView.image = UIImage(data: data!)
    });

Implement protocol "UICollectionViewDataSourcePrefetching" in you ViewController as

class ViewController: UIViewController , UICollectionViewDataSourcePrefetching {

Set following delegates to your collection view in storyboard (see the attached image) or programmatically

In ViewController's viewDidLoad method

collectionView.delegate = self
collectionView.dataSource = self
collectionView.prefetchDataSource = self
Share:
10,710

Related videos on Youtube

Eyeball
Author by

Eyeball

Updated on June 04, 2022

Comments

  • Eyeball
    Eyeball almost 2 years

    I noticed that apps like Intagram uses UICollectionViews to display the feed of photos. I also noticed that the cells for these photos is somehow placed on screen before the actual photos are downloaded completely. Then when a download completes, that photo is nicely displayed in the correct cell.

    I would like to copy that functionality, but I do not know how to go about it.

    I am currently downloading a large JSON object which I transform to an array of NSDictionaries. Each NSDictionary contains information about each photo, and among that information, an URL is presented. At this URL, I can find the corresponding image that I need to download and display in my UICollectionViewCells

    As for now, I iterate this list and initiate a download for each URL I see. When that download is complete, I reload the collectionview using [self.collectionView reloadData]. But, as you can imagine, if I have 30 cells that all wants an image, there is a lot of reloadData calls.

    I am using AFNetworking to handle the download, here is the method, which I call based on the URL I mentioned before:

    -(void) downloadFeedImages:(NSString *) photoURL imageDescs:(NSDictionary*)imageDescs photoId:(NSString *)photoID{
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
        NSString *directory = [paths objectAtIndex:0];
    
        NSString* foofile = [directory stringByAppendingPathComponent:photoID];
    
        if([[NSFileManager defaultManager] fileExistsAtPath:foofile]){
            // IF IMAGE IS CACHED
            [self.collectionView reloadData];
            return;
        }
    
        NSLog(@"photoURL: %@", photoURL);
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:photoURL]];
        AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request
                                                                                  imageProcessingBlock:nil
                                                                                               success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
                                                                                                   // Save Image
                                                                                                   NSLog(@"URL-RESPONSE:%@", image);
                                                                                                   NSString *myFile = [directory stringByAppendingPathComponent:photoID];
    
                                                                                                   NSData *imageData = UIImagePNGRepresentation(image);
                                                                                                   [imageData writeToFile:myFile  atomically: YES];
                                                                                                   [self.collectionView reloadData];
                                                                                               }
                                                                                               failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
                                                                                                   NSLog(@"ERROR: %@", [error localizedDescription]);
                                                                                               }];
        [[WebAPI sharedInstance] enqueueHTTPRequestOperation:operation];
    }
    

    So basically, I wonder how I can achieve the functionality that Instagram and similar applications has when it comes to displaying a feed with images. In addition, I would like to know a good way to initiate a download for each cell, and when that download is finished, update that cell, not redraw the entire view using [reloadData]

    Thanks

  • Eyeball
    Eyeball over 10 years
    However, when i implement this, the collectionview is quite hacky when scrolling. is this method completely asynchronous ? Any idea how to make the scrolling of my collectionview less hacky? =)
  • Amar
    Amar over 10 years
    @Eyeball Yes this is completely asynchronous. Are there any other operations you are performing like FileIO in cellForItemAtIndexPath? Any operations that would cause a noticeable pause in the main thread?
  • Amar
    Amar over 10 years
    One more thing, you need not reload the collection for every image now. The AFNetworking code takes care of refreshing the image view once the image is downloaded and assigned to it.
  • Eyeball
    Eyeball over 10 years
    Spot on, I perform fileIO in cellForItemAtIndexPath. Firstly I check for the correct image in cachesdirectory, if it is there I initialise the image with 'initwithcontentsoffile'-method. If it is not there I download it and then save it to disk 'atomically'. I see now that that this is inefficient and can cause disturbances on the main thread, do you have a suggestion how to solve this? Thank you very much
  • Amar
    Amar over 10 years
    AFNetworking already does caching of downloaded images, so you need not perform that check and then call setImageWithURL: placeholderImage:. Also you need not save the images separately in caches directory.
  • Eyeball
    Eyeball over 10 years
    What if I need that image in another view? I am trying to have as little internet communication as possible. Also, can I set the timer for the cache you mention? Like set the cache time to 30mins? 1hr?
  • Amar
    Amar over 10 years
    You can use the same category function which will first lookup in the AFNetworking cache, if found will use local copy, if not, it will download fresh copy. Your app caches directory will be wiped off if device receives low memory warning, requiring you to re-download and manage the caching. Instead let AFNetworking do it efficiently :)
  • Amar
    Amar over 10 years
    Also AFNetworking will handle resource refresh when caching, Ex it will check the Etag for cached image. If image provider changes the image resource at the same url then AFNetworking will download the modified resource and replace for cached URL.
  • Jakub
    Jakub about 6 years
    No that UICollectionViewDataSourcePrefetching works only on iOS 10+