Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 990
Eyeball Avatar asked Aug 27 '13 06:08

Eyeball


3 Answers

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!

like image 94
Amar Avatar answered Oct 17 '22 01:10

Amar


use

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

instead of

[self.collectionView reloadData];

here indexPath is the NSIndexPath Object for the corresponding UICollectionViewCell Object

like image 31
Anuj Avatar answered Oct 17 '22 03:10

Anuj


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
like image 1
be.with.veeresh Avatar answered Oct 17 '22 01:10

be.with.veeresh