如何在使用自定义单元格的 UICollectionView 中正确设置异步图像下载?

Posted

技术标签:

【中文标题】如何在使用自定义单元格的 UICollectionView 中正确设置异步图像下载?【英文标题】:How do I correctly set up asynchronous image downloading within a UICollectionView that uses a custom cell? 【发布时间】:2014-04-08 19:03:10 【问题描述】:

在这一点上,我真的受够了。现在已经将近一周试图解决这个问题,所以我可以继续前进。我已经阅读了多个线程并就我的缓慢加载、断断续续的 UICollectionView 进行了多次搜索。

我尝试在没有任何库的情况下以及使用 SDWebImage 和 AFNetwork 的情况下做到这一点。它仍然不能解决问题。图像加载并不是真正的问题。当我滚动到当前未在屏幕上显示的单元格时,问题就出现了。

截至目前,我已删除所有库的所有代码和所有痕迹,并希望获得帮助以正确实施此操作。我已经在这方面发表了大约 2 个帖子,这将是我从不同角度进行的第三次尝试。

信息

我的后端数据存储在 Parse.com 我可以通过调用 [self objects] 访问当前加载的对象 我的 cellForItemAtIndex 是修改后的版本,它还返回索引的当前对象。

根据我在 cellForItemAtIndex 中的理解,我需要检查图像,如果没有,我需要在后台线程上下载一个图像并将其设置为显示在单元格中,然后将其副本存储在缓存中这样,如果当我滚动回它时关联的单元格离开屏幕,我可以使用缓存的图像而不是再次下载它。

我的自定义解析 collectionViewController 为我提供了访问下一组对象、当前加载的对象、分页、拉动刷新等所需的所有样板代码。我真的只需要对这个集合视图进行排序。我从来不需要用我以前的应用程序的表格视图来做任何这些,它有更多的图像。花一整天的时间试图解决一个问题却无处可去,真是令人沮丧。

这是我当前的 collectionView cellForItemAtIndex:

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object


    static NSString *CellIdentifier = @"Cell";
    VAGGarmentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: CellIdentifier forIndexPath:indexPath];

 // check for image
 // if there is a cached one use that
 // if not then download one on background thread
 // set my cells image view with that image
 // cache image for re-use.

    // PFFile *userImageFile = object[@"image"];
    [[cell title] setText:[object valueForKey:@"title"]]; //title set
    [[cell price] setText:[NSString stringWithFormat: @"£%@", [object valueForKey:@"price"]]]; //price set

    return cell;


我也在使用自定义 collectionViewCell:

@interface VAGGarmentCell : UICollectionViewCell
@property (weak, nonatomic) IBOutlet UIImageView *imageView;

@property (weak, nonatomic) IBOutlet UITextView *title;
@property (weak, nonatomic) IBOutlet UILabel *price;
@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;

@end

如果您想了解更多信息,请询问。我只想在代码中明确说明如何正确执行此操作,如果它仍然对我不起作用,那么我猜我的代码中的某些地方有问题。

我将继续阅读过去几天遇到的各种线程和资源。我可以说这次体验的一个好处是我对线程和延迟加载有了更好的理解,但我仍然对我在实际应用中取得的任何进展感到非常沮丧。

如果你想知道这是我以前的帖子:In a UICollectionView how can I preload data outside of the cellForItemAtIndexPath to use within it?

我想快速手动完成此操作,或者使用 AFNetwork,因为这不会导致任何错误或需要像 SDWebImage 那样的 hack。

希望你能帮忙

亲切的问候。

【问题讨论】:

AFNetworking 在 UIImageView 上的 setImageFromURL 方法在自定义 collectionviewcell 中对我来说效果很好。 【参考方案1】:

你可以利用 NSURLConnection 使用的内部缓存来做这个。

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath

    VAGGarmentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"VAGGarmentCell" forIndexPath:indexPath];

    //Standard code for initialisation.
    NSURL *url; //The image URL goes here.
        NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:5.0]; //timeout can be adjusted

        [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
         
             if (!connectionError)
             
                 UIImage *image = [UIImage imageWithData:data];

                 //Add image as subview here.
             
         ];

    .
    .
    return cell;


【讨论】:

【参考方案2】:

这是一个表格视图,但基本上是相同的概念。我遇到了和你一样的问题。我必须检查缓存的图像,如果没有,从服务器检索它。需要注意的主要事情是当您检索图像时,您必须在主线程的集合视图中更新它。您还想检查该单元格是否仍然在屏幕上可见。这是我的代码作为示例。 teamMember 是一个字典,@"avatar" 是包含用户图像 URL 的键。 TeamCommitsCell 是我的自定义单元格。

// if  user has an avatar
    if (![teamMember[@"avatar"] isEqualToString:@""]) 
        // check for cached image, use if it exists
        UIImage *cachedImage = [self.imageCache objectForKey:teamMember[@"avatar"]];
        if (cachedImage) 
            cell.memberImage.image = cachedImage;
        
        //else  retrieve the image from server
        else 
            NSURL *imageURL = [NSURL URLWithString:teamMember[@"avatar"]];

            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^
                NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
                // if valid data, create UIImage
                if (imageData) 
                    UIImage *image = [UIImage imageWithData:imageData];
                    // if valid image, update in tableview asynch
                    if (image) 
                        dispatch_async(dispatch_get_main_queue(), ^
                            TeamCommitsCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
                            // if valid cell, display image and add to cache
                            if (updateCell) 
                                updateCell.memberImage.image = image;
                                [self.imageCache setObject:image forKey:teamMember[@"avatar"]];
                            
                        );
                    
                
            );
        
    

【讨论】:

【参考方案3】:

NSURLCache 是 ios 用于缓存检索到的数据(包括图像)的解决方案。在您的 AppDelegate 中,通过以下方式初始化共享缓存:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
    NSURLCache *cache = [[NSURLCache alloc] initWithMemoryCapacity:8 * 1024 * 1024
                                                      diskCapacity:20 * 1024 * 1024
                                                          diskPath:nil];
    [NSURLCache setSharedURLCache:cache];
    return YES;


-(void)applicationDidReceiveMemoryWarning:(UIApplication *)application 
    [[NSURLCache sharedURLCache] removeAllCachedResponses];

然后使用 AFNetworking 的 UIImageView 类别来设置图像使用:

[imageView setImageWithURL:myImagesURL placeholderImage:nil];

事实证明,第二次加载图像的速度非常快。如果您第一次担心更快地加载图像,您将必须创建一种方法来确定要提前加载的时间和数量。使用分页加载数据是很常见的。如果您正在使用寻呼但仍然遇到问题,请考虑使用 AFNetworking 的:

- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
              placeholderImage:(UIImage *)placeholderImage
                       success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
                       failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure;

通过这种方式,您可以创建一个 UIImages 数组,并使用此方法在使单元格出列之前返回每个单元格的图像。因此,在这种情况下,您将有两个并行数组;一个持有您的数据,另一个持有相应的 UIImages。内存管理最终会失控,所以请记住这一点。如果有人快速滚动到可用单元格的底部,老实说,您无能为力,因为数据取决于用户的网络连接。

【讨论】:

你不能只使用UIImageView+AFNetworking而不需要NSURLCache吗?【参考方案4】:

几天后问题是我的图像太大了。我不得不调整它们的大小,这立即解决了我的问题。

我确实缩小了范围并检查了我的图像,发现它们没有通过我认为调整它们大小的方法调整大小。这就是为什么我需要让自己习惯测试。

在过去的几天里,我学到了很多关于 GCD 和缓存的知识,但这个问题本来可以更早解决的。

【讨论】:

以上是关于如何在使用自定义单元格的 UICollectionView 中正确设置异步图像下载?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用填充单元格的 UIImageView 初始化自定义 UICollectionViewCell?

如何在 TableView 中调整自定义单元格的大小?

如何在使用自定义单元格的 UICollectionView 中正确设置异步图像下载?

如何在 Swift 中使用搜索栏过滤具有自定义单元格的 UITableView 的内容?

在没有自定义单元格的情况下向 uitableview 显示详细信息披露按钮

如何从自定义 UITableview 中检索有关选择单元格的数据