Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionView indexPathsForVisibleItems don't update new visible cells

I have a ViewController with a CollectionView inside. When the view loads, the visible cells (9 cells) are shown correctly. When I scroll down, I want to load the visible items in the collectionview with loadImagesForOnscreenRows by calling the indexPathsForVisibleItems for partnerCollectionView. But when loadImagesForOnscreenRows the indexPathsForVisibleItems has allways the first 9 cells in it, even when cells 10 to 18 should be visible on the screen. The code I use is:

#import "PartnerListViewController.h"
#import "AppDelegate.h"
#import "Partner.h"
#import "ImageLoader.h"
#import "PartnerDetailViewController.h"

@interface PartnerListViewController ()

@end

@implementation PartnerListViewController

@synthesize lblTitle;
@synthesize partnerCollectionView;

@synthesize imageDownloadsInProgress;

@synthesize fetchedResultsController;
@synthesize managedObjectContext;

- (void)viewDidLoad
{
   [super viewDidLoad];
   // Do any additional setup after loading the view.

   AppDelegate * appDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
   managedObjectContext = [appDelegate managedObjectContext];
   imageDownloadsInProgress = [NSMutableDictionary dictionary];
   appDelegate = nil;

   [self setupFetchedResultsController];
   [partnerCollectionView reloadData];
}

- (void)didReceiveMemoryWarning
{
   [super didReceiveMemoryWarning];
   // Dispose of any resources that can be recreated.
}

#pragma mark - Collection view data source

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return [[fetchedResultsController sections] count];
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
   id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
   return [sectionInfo numberOfObjects];
}

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

   Partner *partner = [self.fetchedResultsController objectAtIndexPath:indexPath];

   UIImageView *imageView = (UIImageView *)[cell viewWithTag:100];
   if (!partner.image)
   {
       imageView.image = [UIImage imageNamed:@"annotationMap.png"];
       if (self.partnerCollectionView.dragging == NO && self.partnerCollectionView.decelerating == NO)
       {
           [self startDownload:partner.imageUrl forIndexPath:indexPath];
       }
   } else {
       imageView.image = [UIImage imageWithData:partner.image];
   }

   UILabel *lblTitlePartner = (UILabel *)[cell viewWithTag:101];
   lblTitlePartner.text = partner.title;
   lblTitlePartner.font = [UIFont fontWithName:fontName size:10];

   return cell;
}

#pragma mark - Table cell image support
- (void)startDownload:(NSString *)urlString forIndexPath:(NSIndexPath *)indexPath
{
   NSLog(@"startDownload:%ld", (long)indexPath.row);

   ImageLoader *imageLoader = [imageDownloadsInProgress objectForKey:indexPath];
   imageLoader = [[ImageLoader alloc] init];
   imageLoader.urlString = urlString;
   imageLoader.indexPathTableView = indexPath;
   imageLoader.delegate = (id)self;
   [imageDownloadsInProgress setObject:imageLoader forKey:indexPath];
   [imageLoader startDownload];
}

// this method is used in case the user scrolled into a set of cells that don't have their app icons yet
- (void)loadImagesForOnscreenRows
{
   NSArray *visiblePaths = [self.partnerCollectionView indexPathsForVisibleItems];
   NSMutableArray *rowsArray = [NSMutableArray arrayWithCapacity:[visiblePaths count]];
   [visiblePaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
       NSLog(@"loadImagesForOnscreenRows1%@", @(indexPath.item));
       [rowsArray addObject:@(indexPath.item)];
   }];
   for (NSIndexPath *indexPath in visiblePaths)
   {
       NSLog(@"loadImagesForOnscreenRows2:%ld", (long)indexPath.row);

       Partner *item = [self.fetchedResultsController objectAtIndexPath:indexPath];

       if (!item.image) // avoid the app icon download if the app already has an icon
       {
           [self startDownload:item.imageUrl forIndexPath:indexPath];
       }
   }
}

// called by our ImageDownloader when an icon is ready to be displayed
- (void)imageLoaderDidFinishDownloading:(NSIndexPath *)indexPath
{
   NSLog(@"imageLoaderDidFinishDownloading:%ld", (long)indexPath.row);

   ImageLoader *imageLoader = [imageDownloadsInProgress objectForKey:indexPath];
   if (imageLoader != nil)
   {
       // Save the newly loaded image
       Partner *item = [self.fetchedResultsController objectAtIndexPath:indexPath];
       item.image = UIImageJPEGRepresentation(imageLoader.image, 1.0);

       [self performSelectorOnMainThread:@selector(saveItem) withObject:nil waitUntilDone:YES];
       [self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES];
   }
}

- (void)saveItem
{
   [self.managedObjectContext save:nil];
}

- (void)reloadData
{
   [self.partnerCollectionView reloadData];
}

#pragma mark deferred image loading (UIScrollViewDelegate)

// Load images for all onscreen rows when scrolling is finished
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
   if (!decelerate)
{
       [self loadImagesForOnscreenRows];
   }
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
   [self loadImagesForOnscreenRows];
}

- (void)setupFetchedResultsController
{
   // 1 - Decide what Entity you want
   NSString *entityName = @"Partner"; // Put your entity name here
   NSLog(@"Setting up a Fetched Results Controller for the Entity named %@", entityName);

   // 2 - Request that Entity
   NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];

   // 4 - Sort it if you want
   request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"title" ascending:NO selector:@selector(localizedCaseInsensitiveCompare:)]];
   // 5 - Fetch it
   self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
   NSError *error = nil;
  if (![[self fetchedResultsController] performFetch:&error]) {
      NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
   }
}

@end

And this result in the output:

Initial show of visible items

2013-09-02 06:45:21.940 [2564:c07] startDownload:0
2013-09-02 06:45:21.943 [2564:c07] imageLoaderDidFinishDownloading:0
2013-09-02 06:45:21.950 [2564:c07] startDownload:1
2013-09-02 06:45:21.951 [2564:c07] imageLoaderDidFinishDownloading:1
2013-09-02 06:45:21.958 [2564:c07] startDownload:2
2013-09-02 06:45:21.959 [2564:c07] imageLoaderDidFinishDownloading:2
2013-09-02 06:45:21.965 [2564:c07] startDownload:3
2013-09-02 06:45:22.063 [2564:c07] imageLoaderDidFinishDownloading:3
2013-09-02 06:45:22.072 [2564:c07] startDownload:4
2013-09-02 06:45:22.073 [2564:c07] imageLoaderDidFinishDownloading:4
2013-09-02 06:45:22.081 [2564:c07] startDownload:5
2013-09-02 06:45:22.082 [2564:c07] imageLoaderDidFinishDownloading:5
2013-09-02 06:45:22.089 [2564:c07] startDownload:6
2013-09-02 06:45:22.090 [2564:c07] imageLoaderDidFinishDownloading:6
2013-09-02 06:45:22.098 [2564:c07] startDownload:7
2013-09-02 06:45:22.099 [2564:c07] imageLoaderDidFinishDownloading:7
2013-09-02 06:45:22.104 [2564:c07] startDownload:8
2013-09-02 06:45:22.163 [2564:c07] imageLoaderDidFinishDownloading:8

After scrolling to item 10 to 19:

2013-09-02 06:45:26.212 [2564:c07] loadImagesForOnscreenRows1:8
2013-09-02 06:45:26.212 [2564:c07] loadImagesForOnscreenRows1:0
2013-09-02 06:45:26.212 [2564:c07] loadImagesForOnscreenRows1:1
2013-09-02 06:45:26.212 [2564:c07] loadImagesForOnscreenRows1:6
2013-09-02 06:45:26.213 [2564:c07] loadImagesForOnscreenRows1:2
2013-09-02 06:45:26.213 [2564:c07] loadImagesForOnscreenRows1:3
2013-09-02 06:45:26.213 [2564:c07] loadImagesForOnscreenRows1:4
2013-09-02 06:45:26.213 [2564:c07] loadImagesForOnscreenRows1:5
2013-09-02 06:45:26.213 [2564:c07] loadImagesForOnscreenRows1:7
2013-09-02 06:45:26.214 [2564:c07] loadImagesForOnscreenRows2:8
2013-09-02 06:45:26.214 [2564:c07] loadImagesForOnscreenRows2:0
2013-09-02 06:45:26.214 [2564:c07] loadImagesForOnscreenRows2:1
2013-09-02 06:45:26.214 [2564:c07] loadImagesForOnscreenRows2:6
2013-09-02 06:45:26.214 [2564:c07] loadImagesForOnscreenRows2:2
2013-09-02 06:45:26.215 [2564:c07] loadImagesForOnscreenRows2:3
2013-09-02 06:45:26.215 [2564:c07] loadImagesForOnscreenRows2:4
2013-09-02 06:45:26.215 [2564:c07] loadImagesForOnscreenRows2:5
2013-09-02 06:45:26.215 [2564:c07] loadImagesForOnscreenRows2:7

As you can see after scrolling, the items visible index paths do stay the same. Has anybody else encountered this or an idea for a solution? Or am I unsterstanding some principle of collectionview wrong?

Many thanks in advance! Kind regards, Jan

like image 970
Jan Stulens Avatar asked Sep 03 '13 14:09

Jan Stulens


1 Answers

If you are targeting iOS 8.0 and above you should use collectionView:willDisplayCell:forItemAtIndexPath: to kick off your download. If using iOS 7.0 you should continue to use collectionView:cellForItemAtIndexPath:.

In your imageLoaderDidFinishDownloading: callback you should check to see if the index path in question is still visible. If it is, retrieve the corresponding cell and update its image view. If the cell isn't visible, then your work is done. Calling -reloadData for every image completion is doing a lot of expensive work and could have significant UX issues if your user is currently mid-scroll of the table and you reset its contents. You are also potentially doing the UIImageJPEGRepresentation() work many times, it would help your scrolling performance if you did this work once in imageLoaderDidFinishDownloading: and then cached it.

Since it looks like the callback happens on a background thread make sure you only manipulate the UICollectionView from the main thread.

like image 192
jszumski Avatar answered Oct 21 '22 06:10

jszumski