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

时间:2014-04-08 19:03:10

标签: ios objective-c cocoa-touch uicollectionview lazy-loading

此时我真的厌倦了。现在已经有将近一周时间试图解决这个问题,所以我可以继续前进。我已经阅读了多个线程,并针对我的慢速加载乱码UICollectionView进行了多次搜索。

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

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

信息

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

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

我的自定义解析collectionViewController为我提供了访问下一组对象,当前加载的对象,分页,拉动刷新等所需的所有样板代码。我真的只需要对此集合视图进行排序。我从来没有需要用我之前的应用程序的tableview来做任何这个,它有更多的图像。花一整天的时间试图解决问题并且没有任何地方真的令人沮丧。

这是我当前的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那样的黑客攻击。

希望你能帮忙

亲切的问候。

4 个答案:

答案 0 :(得分:4)

您可以使用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;

}

答案 1 :(得分:0)

这是一个表视图,但基本上是相同的概念。我遇到了同样的问题。我必须检查缓存的图像,如果没有,请从服务器检索它。要注意的主要问题是当您检索图像时,必须在主线程的集合视图中更新它。您还想检查屏幕上是否仍然可以看到该单元格。这是我的代码作为示例。 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"]];
                            }
                        });
                    }
                }
            });
        }
    }

答案 2 :(得分:0)

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。内存管理最终会失控,所以记住这一点。如果有人快速滚动到可用单元格的底部,实际上没有其他可以做的事情,因为数据取决于用户的网络连接。

答案 3 :(得分:0)

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

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

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