Search code examples
iosasynchronousuiimageviewuicollectionviewnsmutableurlrequest

How to stop Automatic reloading and refreshing of images in CollectionView iOS


I am trying to display some images from URL into my collection view. But when i try to do so, my images get displayed on collection view but they randomly keep refreshing and reloaded. Also when i try to scroll the collectionView, it again reloads the whole data, which i don't want. I just want to display images like a preview to user. Here below is my code to fetch the images from URL and display it on collection View.

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:@"cellIdentifier" forIndexPath:indexPath];

 __block  NSString *filename;     

for(NSDictionary *item in [jsonSD objectForKey:@"Data"]) {       
      for (int i=0; i<=[[item objectForKey:@"Count"]integerValue]; i++) {
         filename=[NSString stringWithFormat:@"http://..URL../media/content/%@%@%d_1.png",_getstring,[item objectForKey:@"subcategory_id"],i];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{     
              NSURL *url = [NSURL URLWithString:filename];
                  [self downloadImageWithURL:url completionBlock:^(BOOL succeeded, NSData *data) {
                                if (succeeded) {
                                    iv.image = [[UIImage alloc] initWithData:data];                                        
                                }
                                    }];

                      });
    }
  }

return cell;
}

- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, NSData *data))completionBlock
{
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
    if (!error) {
        completionBlock(YES, data);
    } else {
        completionBlock(NO, nil);
    }
}];
}

Here 'iv' is my imageView, where i display the images on cell of collection view. I tried searching related contents but i didn't got any idea for above problem.

Please help me. Any help is appreciated.


Solution

  • So seems like there's two issues here:

    The first problem is with:

    cellForItemAtIndexPath
    

    gets called every single time the cell appears on the screen, which means if you're scrolling then downloadImageWithURL is going to get called every single time a cell appears on the screen.

    The second issue seems to be that multiple images are being loaded for each cell with for (int i=0; i<=[[item objectForKey:@"Count"]integerValue]; i++)

    This loop seems to download multiple images and then sets them in turn on the same cell?

    Some solutions to the first issue:

    1. If you only need to display a few items, load all the images in viewDidLoad and update the datasource every single time one of the images has downloaded.
    2. After the image for each cell has downloaded, add it to an array. Every time a cell loads, check if the array contains and image for that cell and use that instead of downloading a new one.

    To the second problem:

    Unsure of what the loop is meant to achieve here, do you want each image from the loop to be displayed in it's own collection view cell?

    Updated:

    Here's a quick example to show you how the code might look. I have no idea what's contained in jsonSD or some of the other parts of your code, but it should give you the right idea:

    @interface TestCollectionViewController () {
    NSMutableArray *datasource;
    NSDictionary *jsonSD;
    }
    
    
    @end
    
    @implementation TestCollectionViewController
    
    static NSString *const reuseIdentifier = @"Cell";
    
    - (void)viewDidLoad {
    [super viewDidLoad];
    
    // We want to load the data from all cells as soon as the view loads, instead of loading the data as the cells scroll.
    // Lazy loading cells will require a more advanced implementation
    
    [self loadDatasource];
    
    }
    
    - (void)loadDatasource {
    
    for (NSDictionary *item in [jsonSD objectForKey:@"Data"]) {
    
        // Fill the datasource with null objects which match the total number of objects you want to display
    
        datasource = [NSMutableArray arrayWithCapacity:[[item objectForKey:@"Count"] integerValue]];
    
        for (int i = 0; i <= [[item objectForKey:@"Count"] integerValue]; i++) {
    
            [datasource addObject:[NSNull null]];
    
        }
    
        // Reload the collectionview now so that the empty cells will be displayed with no image.
    
        [self.collectionView reloadData];
    
        // Load each image individually and replace the corresponding NSNull in the datasource.
    
        for (int i = 0; i <= [[item objectForKey:@"Count"] integerValue]; i++) {
    
            NSString *filename = [NSString stringWithFormat:@"http://..URL../media/content/%@%@%d_1.png", _getstring, [item objectForKey:@"subcategory_id"], i];
    
            NSURL *url = [NSURL URLWithString:filename];
            [self downloadImageWithURL:url completionBlock:^(BOOL succeeded, NSData *data) {
                if (succeeded) {
    
                    [datasource replaceObjectAtIndex:i withObject:[UIImage imageWithData:data]];
    
                    // Reload the collectionview after each item has downloaded so the image gets displayed in the cell.
    
                    [self.collectionView reloadData];
                }
            }];
    
        }
    }
    }
    
    - (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, NSData *data))completionBlock {
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        if (!error) {
            completionBlock(YES, data);
        } else {
            completionBlock(NO, nil);
        }
    }];
    }
    
    #pragma mark <UICollectionViewDataSource>
    
    - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    
    // Only displaying one section
    
    return 1;
    }
    
    - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return datasource.count;
    }
    
    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
    
    // Configure the cell
    
    if ([datasource objectAtIndex:indexPath.row] == [NSNull null]) {
    
        // Display a default image here
    
    } else {
    
        // Image has been loaded, display it in the imageview
    
        iv.image = [datasource objectAtIndex:indexPath.row];
    }
    
    return cell;
    }
    

    Update 2:

    The answer above isn't terrible efficient and won't work too well if you have hundreds of items. For a more solid implementation, i'd recommend using a library like SDWebImage to handle it for you. Then you can do something as simple as:

    [cell.imageView setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
               placeholderImage:[UIImage imageNamed:@"placeholder.png"]
                      completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {... completion code here ...}];