在 UICollectionView 中,如何预加载 cellForItemAtIndexPath 之外的数据以在其中使用?
Posted
技术标签:
【中文标题】在 UICollectionView 中,如何预加载 cellForItemAtIndexPath 之外的数据以在其中使用?【英文标题】:In a UICollectionView how can I preload data outside of the cellForItemAtIndexPath to use within it? 【发布时间】:2014-04-06 09:33:11 【问题描述】:发现加载缓慢的图像是我的 collectionView 不稳定的缓慢效果背后的原因。 我整天都在阅读不同的问答和各种论坛帖子。看起来解决此问题的最佳方法是为 cellForItemAtIndexPath 预加载数据,以便能够获取所需的数据。
我不确定我该怎么做。我使用 parse 作为我的后端,但如果给出一个粗略的例子,我肯定能够弄清楚如何做到这一点。从我目前看到的情况来看,我需要一个单独的方法来获取数据。
代码如下:
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
return 1;
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
return [[self objects] count];
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
NSArray *people = [self objects];
static NSString *CellIdentifier = @"Cell";
VAGGarmentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: CellIdentifier forIndexPath:indexPath];
PFObject *current;
current = [people objectAtIndex:indexPath.item];
PFFile *userImageFile = current[@"image"];
[userImageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error)
UIImage *image = [UIImage imageWithData:imageData];
[[cell contentView] setContentMode: UIViewContentModeScaleAspectFit];
[[cell imageView] setImage:image];
];
[[cell title] setText:[current valueForKey:@"title"]];
[[cell price] setText:[NSString stringWithFormat: @"£%@", [current valueForKey:@"price"]]];
return cell;
所以也许 cellForItemAtIndexPath 需要调用该方法并获取它需要的东西。因为数据已经可用,所以不需要在 cellForItemAtIndexPath 方法中加载,并且会立即填充单元格。
请给出建议和例子。
有人告诉我这样做的一个好方法是检查图像,如果不存在则提供占位符,如果存在则设置它。以下是对上述代码的更改。
更新:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
NSArray *people = [self objects];
static NSString *CellIdentifier = @"Cell";
VAGGarmentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: CellIdentifier forIndexPath:indexPath];
PFObject *current;
current = [people objectAtIndex:indexPath.item];
PFFile *userImageFile = current[@"image"];
[userImageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error)
if (!error)
if (!image)
[[cell imageView] setImage:[UIImage imageNamed:@"placeholder.png"]];
else
image = [UIImage imageWithData:imageData];
//resize image
CGSize destinationSize = CGSizeMake(158,187);
UIGraphicsBeginImageContext(destinationSize);
[image drawInRect:CGRectMake(0,0,destinationSize.width, destinationSize.height)];
//New image
UIImage*newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//Optimise image
NSData *imageDataCompressed = UIImageJPEGRepresentation(newImage, 0.4f);
// NSLog(@"Image Size %@", NSStringFromCGSize(newImage.size));//log size of image
NSLog(@"%@", [current valueForKey:@"title"]);
[[cell imageView] setImage:[UIImage imageWithData:imageDataCompressed]];
];
[[cell title] setText:[current valueForKey:@"title"]];
[[cell price] setText:[NSString stringWithFormat: @"£%@", [current valueForKey:@"price"]]];
return cell;
占位符显示正常但仍然存在,我如何知道图像何时已加载,以便让我的单元格反映这一点?
感谢您的宝贵时间。 亲切的问候。
更新:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
NSArray *people = [self objects];
static NSString *CellIdentifier = @"Cell";
VAGGarmentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: CellIdentifier forIndexPath:indexPath];
[cell.activityIndicator startAnimating];
PFObject *current;
current = [people objectAtIndex:indexPath.item];
PFFile *userImageFile = current[@"image"];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:userImageFile.url, indexPath.item]];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataDontLoad
timeoutInterval:6.0];
[cell.imageView setImageWithURLRequest:urlRequest
placeholderImage:[UIImage imageNamed:@"placeholder.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image)
//resize image
CGSize destinationSize = CGSizeMake(158,187);
UIGraphicsBeginImageContext(destinationSize);
[image drawInRect:CGRectMake(0,0,destinationSize.width, destinationSize.height)];
//New image
UIImage*newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//Optimise image
NSData *imageDataCompressed = UIImageJPEGRepresentation(newImage, 0.4f);
cell.imageView.image = [UIImage imageWithData:imageDataCompressed];
NSLog(@"Image Size %@", NSStringFromCGSize(newImage.size));//log size of image
[cell.activityIndicator stopAnimating];
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error)
NSLog(@"Failed to download image: %@", error);
];
return cell;
最新更新:
设置一个从 parse.com 获取数据并存储在 NSMutableDictionary 中然后存储在可变数组中的方法。我存储服装图像的标题、价格和 URL。
- (void)grabDataFromCloud
self.model = [NSMutableArray array];
for (PFObject *object in [self objects])
PFFile *imageFile = [object valueForKey:@"image"];
NSURL *url = [NSURL URLWithString:imageFile.url];
NSMutableDictionary *newObject = [NSMutableDictionary dictionaryWithDictionary:@@"title": [object valueForKey:@"title"], @"price": [object valueForKey:@"price"], @"imageUrl": url];
[[self model] addObject:newObject];
这在我的 cellForItemsAtIndexPath 方法中被调用。
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
[self grabDataFromCloud];
static NSString *CellIdentifier = @"Cell";
VAGGarmentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: CellIdentifier forIndexPath:indexPath];
[cell.activityIndicator setHidden:YES];
NSMutableDictionary* d = [self.model objectAtIndex:indexPath.item];
cell.title.text = d[@"title"];
cell.price.text = [NSString stringWithFormat:@"£%@", d[@"price"]];
if (d[@"image"])
cell.imageView.image = d[@"image"];
else // if not, download it
cell.imageView.image = nil;
dispatch_queue_t backgroundQueue = dispatch_queue_create("test", 0);
dispatch_async(backgroundQueue, ^
NSData* data = [NSData dataWithContentsOfURL:d[@"imageUrl"]];
UIImage* img = [UIImage imageWithData:data];
d[@"image"] = img;
dispatch_async(dispatch_get_main_queue(), ^
//causes crash Assertion failure in -[UICollectionView _endItemAnimations],
// /SourceCache/UIKit/UIKit-2935.137/UICollectionView.m:3687
// [self.collectionView reloadItemsAtIndexPaths:@[indexPath]];
);
);
return cell;
【问题讨论】:
【参考方案1】:我建议您使用AFNetworking
的UIImageView+AFNetworking
类别。它将自动处理占位符等,并将在后台线程中执行所有操作,确保主线程不会被阻塞。具体来说,这是您要调用的方法:
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholderImage;
【讨论】:
我一直试图让它工作,它在缓存之外工作。即使我已将缓存策略设置为 NSURLRequestReturnCacheDataElseLoad,图像仍会继续下载。看起来图像已下载,但当我滚动时,我仍然得到斩波效果。【参考方案2】:您可以在首先需要图像时提供占位符图像(或 nil)并开始下载它,然后挂在图像上 下载后,您就可以立即提供它。这个例子是针对表格视图的,但是原理是完全一样的;关键是我的数据模型是一堆 NSMutableDictionary 对象,每个字典不仅包含我们应该拥有的图片的 url,而且还有一个保存图像的地方 下载:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
NSMutableDictionary* d = (self.model)[indexPath.row];
cell.textLabel.text = d[@"text"];
if (d[@"im"]) // if we have a picture, supply it
cell.imageView.image = d[@"im"];
else if (!d[@"task"]) // if not, download it
cell.imageView.image = nil;
NSURLSessionTask* task = [self.downloader download:d[@"picurl"]
completionHandler:^(NSURL* url)
if (!url)
return;
NSData* data = [NSData dataWithContentsOfURL:url];
UIImage* im = [UIImage imageWithData:data];
d[@"im"] = im;
dispatch_async(dispatch_get_main_queue(), ^
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
);
];
return cell;
【讨论】:
该代码来自一个完整的可下载示例代码项目,您可以在自己的机器上运行并查看它确实有效(当然,对于表格视图):github.com/mattneub/Programming-ios-Book-Examples/tree/master/… 试验了一段时间。我将我的数据加载到一个 NSDictionary 中,该 NSDictionary 存储在一个可变的中。这一切都放入了由 cellForItemAtIndex 方法触发的方法中。标题和价格更新得很好。我检查图像,如果没有图像,我会尝试添加图像。我记录了我的 img var 以查看图像是否真的存在,是的图像存储在 var 中。我现在遇到的最后一个问题是在单元格上显示该图像。我试图在 indexpaths 处重新加载行,但这会导致应用程序崩溃。您可以在 dispatch_get_main_queue 中看到上述代码中的错误消息。 一两分钟后才发现应用程序本身崩溃了。【参考方案3】:我建议采用不同的方法。
谷歌 - 或使用搜索 SO - 异步加载。几乎每个应用程序程序员早晚都会面临这个问题。因此,那里有大量的教程。
这是其中之一。
http://www.markj.net/iphone-asynchronous-table-image/
我认为它比UICollectionView
旧,因此为UITableView
解释它。两个数据源委托彼此非常接近,您可以轻松地将解决方案应用于您的集合。
有更聪明的方法可以实现您的目标。但我认为这种方式是一个很好的起点。一旦您对一般方法感到满意,您以后可能想要重构解决方案。
【讨论】:
好的,检查了一个页面,它碰巧是错误的页面。我看了看那个代码。我明白需要做什么,只是如何去做。现在我的 cellForItemAtIndexPath 正在使用 URL 抓取项目。然后它们被缓存,但切割仍在发生。我已经缩小了图像的大小,但没有效果。 当使用 URL 抓取它们时,你会异步执行吗? 我确定我是。请检查我的问题代码的最新更新。这是显示最新的代码。 您的最后一次编辑对我来说看起来不错。只有一件事。第一个完全加载的图像,在许多被请求的图像中,将停止活动指示器,尽管其他图像仍在播放。但这只是装饰性的,与您的问题无关。【参考方案4】:几天后问题是我的图像太大了。我不得不调整它们的大小,这立即解决了我的问题。
我确实缩小了范围并检查了我的图像,发现它们没有通过我认为调整它们大小的方法调整大小。这就是为什么我需要让自己习惯测试。
在过去的几天里,我学到了很多关于 GCD 和缓存的知识,但这个问题本来可以更早解决的。
【讨论】:
以上是关于在 UICollectionView 中,如何预加载 cellForItemAtIndexPath 之外的数据以在其中使用?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 UICollectionView 的补充视图中触发刷新
如何在 UICollectionViewCell 中添加 UICollectionView?
如何在 iOS 中动态更改 UICollectionView 单元格高度