UICollectionView indexPathsForVisibleItems 不更新新的可见单元格

Posted

技术标签:

【中文标题】UICollectionView indexPathsForVisibleItems 不更新新的可见单元格【英文标题】:UICollectionView indexPathsForVisibleItems don't update new visible cells 【发布时间】:2013-09-03 14:55:01 【问题描述】:

我有一个 ViewController,里面有一个 CollectionView。当视图加载时,可见单元格(9 个单元格)正确显示。当我向下滚动时,我想通过调用 partnerCollectionView 的 indexPathsForVisibleItems 来使用 loadImagesForOnscreenRows 加载 collectionview 中的可见项。但是当 loadImagesForOnscreenRows 时 indexPathsForVisibleItems 总是包含前 9 个单元格,即使第 10 到 18 个单元格应该在屏幕上可见。我使用的代码是:

#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

这会导致输出:

可见项目的初始显示

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

滚动到第 10 到 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

正如您在滚动后看到的那样,项目可见的索引路径确实保持不变。有没有其他人遇到过这个或解决方案的想法?还是我理解collectionview的一些原则错了?

提前非常感谢! 亲切的问候, 一月

【问题讨论】:

我不明白loadImagesForOnscreenRows 的目的。在cellForRowAtIndexPath 中设置图像(如果可用)。否则,在imageLoaderDidFinishDownloading 中,您可以重新加载索引路径,这将再次调用cellForRowAtIndexPath,或者如果图像仍在屏幕上,您可以直接在单元格上设置图像。这不会覆盖它吗? 嗨蒂莫西,cellForRowAtIndexPath 加载视图中可见的单元格,在我的例子中是前九个。当我开始滚动时,即将到来的单元格不会被填充,而是调用scrollViewDidEndDragging,它将调用loadImagesForOnscreenRows,就像在表格视图中一样。在这个函数中,我想获取可见的索引路径indexPathsForVisibleItems,并开始下载图像,一旦下载,重新加载索引路径。但是indexPathsForVisibleItems 总是返回前九个。还是我做错了什么。对于表格,这种方式可以正常工作。 没有图像的单元格通常会在该单元格的下载完成时单独填充,在您的情况下会发生在imageLoaderDidFinishDownloading 中。鉴于此,我不明白loadimageforOnscreenRows 的意思。换句话说,你为什么不填写imageLoaderDidFinishDownloading的单元格? 当我加载我的视图时,cellForRowAtIndexPath 只被称为前九个单元格,即可见的单元格。 NSLogin cellForRowAtIndexPath 只打印 0 到 8。因此其他单元格不会开始下载,在获取结果后重新加载 collectionView 只会影响可见单元格。这就是为什么我教我应该在scrollViewDidEndDragging 上调用loadimageforOnscreenRows,这样它就可以加载向下滚动后变得可见的单元格的图像。因为如果我将scrollViewDidEndDragging 放在评论中,滚动后它完全没有任何作用。 :) cellForRowAtIndexPath 总是在单元格显示在屏幕上之前被调用。否则,将没有任何东西可以显示。此外,您似乎没有实现 NSFetchedResultsControllerDelegate 方法,所以我不清楚您为什么还要使用 NSFetchedResultsController,因为没有委托,它无法控制任何东西。我错过了什么吗? 【参考方案1】:

如果您的目标是 ios 8.0 及更高版本,您应该使用 collectionView:willDisplayCell:forItemAtIndexPath: 开始下载。如果使用 iOS 7.0,您应该继续使用collectionView:cellForItemAtIndexPath:

在您的imageLoaderDidFinishDownloading: 回调中,您应该检查相关索引路径是否仍然可见。如果是,则检索相应的单元格并更新其图像视图。如果单元格不可见,那么您的工作就完成了。为每个图像完成调用-reloadData 会做大量昂贵的工作,并且如果您的用户当前正在表格的中间滚动并且您重置其内容,则可能会出现严重的用户体验问题。您还可能多次执行UIImageJPEGRepresentation() 工作,如果您在imageLoaderDidFinishDownloading: 中执行此工作一次然后缓存它,这将有助于您的滚动性能。

由于看起来回调发生在后台线程上,请确保您只在主线程中操作 UICollectionView

【讨论】:

以上是关于UICollectionView indexPathsForVisibleItems 不更新新的可见单元格的主要内容,如果未能解决你的问题,请参考以下文章

UITableView 内存问题

UITableView 方法“indexPathForRowAtPoint:”的奇怪行为

使用 Alamofire 加载图像非常慢

Swift 分段控制和 UITableView

外部获取IndexPath的几种方式(关联对象等)

照片框架:RequestImageForAsset 方法中的 contentMode