我意识到已经存在很多这些问题,但似乎没有一个问题可以解决这个问题。
CollectionView从files
数组中读取文件名,并从Documents目录中加载图像或下载它们,显示并保存到Documents。工作非常流畅,没有任何额外的库,但是当快速滚动时,几个单元格会收到错误的图像。使用任何操作调用[collectionView reloadData]
或向后滚动会将这些错误的图像重新加载到好的图像中。
我认为它与重复使用异步图像分配的单元格有关,但是如何解决这个问题呢?在Storyboard中定义的CollectionView和自定义单元格。图像存储在基本身份验证后面的服务器上,因此大多数重用解决方案都不适用。
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
BrowseCollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:@"MyCell" forIndexPath:indexPath];
NSString *fileName = [files objectAtIndex:indexPath.item];
NSString *filePath = [thumbDir stringByAppendingPathComponent:fileName];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
cell.imageView.image = [UIImage imageWithContentsOfFile:filePath];
}
else //DOWNLOAD
{
NSString *strURL = [NSString stringWithFormat:@"%@%@", THUMBURL, fileName];
NSURL *fileURL = [NSURL URLWithString:strURL];
cell.imageView.image = [UIImage imageNamed:@"placeholder.jpg"];
[self downloadFromURL:fileURL to:filePath
completionBlock:^(BOOL succeeded, UIImage *image) {
if (succeeded)
{
cell.imageView.image = image;
}
}];
}
}
cell.imageName = fileName;
return cell;
}
- (void)downloadFromURL:(NSURL*)url to:(NSString *)filePath completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
NSString *authStr = [NSString stringWithFormat:@"%@:%@", LOGIN, PASS];
NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
NSString *authValue = [authData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadRevalidatingCacheData timeoutInterval:30];
[request setValue:[NSString stringWithFormat:@"Basic %@", authValue] forHTTPHeaderField:@"Authorization"];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error)
{
UIImage *image = [[UIImage alloc] initWithData:data];
[data writeToFile:filePath atomically:NO];
completionBlock(YES, image);
} else {
completionBlock(NO, nil);
}
}];
}
我尝试了很多修改,但似乎都没有解决问题。目前,效果是快速滚动期间出现的新单元格被多次更改,其中一些单元格总是以错误的图像结束,指向一些重用问题。
提前感谢您的任何帮助和建议。
答案 0 :(得分:14)
问题在于,当快速滚动时,在异步请求完成时,单元可能会出列并重新用于另一个单元,从而更新错误的单元。因此,在完成块中,您应确保单元格仍然可见:
[self downloadFromURL:fileURL to:filePath completionBlock:^(BOOL succeeded, UIImage *image) {
if (succeeded) {
BrowseCollectionViewCell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];
if (updateCell) { // if the cell is still visible ...
updateCell.imageView.image = image; // ... then update its image
}
}
}];
请注意,不应将此UICollectionView
方法cellForItemAtIndexPath
与名称相似的UICollectionViewDataSource
方法collectionView:cellForItemAtIndexPath:
混淆。如果单元格仍然可见,则UICollectionView
方法cellForItemAtIndexPath
会返回UICollectionViewCell
,如果不可见,则返回nil
。
顺便说一下,上面假设这个单元格的NSIndexPath
不能改变(即,在异步检索图像时,不可能在该行上方插入额外的行)。这有时不是一个有效的假设。因此,如果您想要小心,那么您真正应该做的是返回模型并重新计算此模型对象的NSIndexPath
,并在确定适当的updateCell
引用时使用它。
理想情况下,一旦解决了上述问题,您可以对此流程进行一些优化:
如果在上一个请求仍在运行时重复使用该单元格,则可能需要取消该先前请求。如果你没有,你快速滚动,比如200个单元格,现在显示单元格201到221,那么对那20个当前可见单元格的请求将排队,直到前200个请求完成才会显示。
但是,为了能够取消先前的请求,您无法使用sendAsynchronousRequest
。您必须使用可取消的基于代理的NSURLConnection
或NSURLSessionTask
。
您可能应该缓存图片。如果您当前可见的单元格都有自己的图像,然后将它们滚动并重新打开,则您似乎正在重新请求它们。您应首先查看是否已检索到图像,如果已经检索,请使用该图像,并且只有在缓存中没有图像时才重新发出请求。
是的,我知道您正在使用持久存储中的文件版本进行缓存,但您也可能希望缓存到RAM。通常人们会为此目的使用NSCache
。
这要改变很多。如果您需要帮助,请告诉我们。但更容易的是使用SDWebImage或AFNetworking中的UIImageView
类别,它会为您完成所有这些。
答案 1 :(得分:13)
您需要在某处保留一些上下文信息。换句话说,在完成块中,检查单元格是否确实包含下载的图像。如果没有,请不要分配它。例如图像URL。你可以这样做......
NSURL *imageURL
添加到您的单元格对象downloadFromURL:...
商店fileURL
至imageURL
小区属性之前fileURL
是否与cell.imageURL
匹配,如果匹配,则设置图像,否则不设置nil
imageURL
prepareForReuse
属性
...或者您可以将加载代码移动到您的单元格类中,并为其分配图像的URL等。许多方法都可以。
答案 2 :(得分:2)
您需要在块替换中获取单元格的引用:
[self downloadFromURL:fileURL to:filePath
completionBlock:^(BOOL succeeded, UIImage *image) {
if (succeeded)
{
cell.imageView.image = image;
}
}];
与
[self downloadFromURL:fileURL to:filePath
completionBlock:^(BOOL succeeded, UIImage *image) {
BrowseCollectionViewCell *innerCell = (BrowseCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
if (succeeded)
{
innerCell.imageView.image = image;
}
}];