简体   繁体   English

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

[英]How do I correctly set up asynchronous image downloading within a UICollectionView that uses a custom cell?

At this point I'm really fed up. 在这一点上我真的受够了。 It's been nearly a week now trying to solve this issue so I can move ahead. 现在已经有将近一周的时间试图解决这个问题,因此我可以继续前进。 I've read multiple threads and done multiple searches in regards to my slow loading choppy UICollectionView. 关于慢速加载的不稳定UICollectionView,我已经阅读了多个线程并进行了多次搜索。

I've tried to do this without any libraries as well as with SDWebImage and AFNetwork. 我尝试不使用任何库以及使用SDWebImage和AFNetwork来执行此操作。 It still doesn't fix things. 它仍然不能解决问题。 Images loading isn't really a problem. 图像加载并不是真正的问题。 The problem arrives when I scroll to cells that aren't currently showing on the screen. 当我滚动到屏幕上当前未显示的单元格时,问题就来了。

As of now I've deleted all the code and all traces of any libraries and would like to get help in order to implement this properly. 到目前为止,我已经删除了所有库的所有代码和所有痕迹,并希望获得帮助以正确实现此目的。 I've made about 2 posts on this already and this would be my third attempt coming from a different angle. 我已经在此发表了大约2篇文章,这是我第三次尝试来自不同的角度。

Information 信息

My backend data is stored on Parse.com I have access to currently loaded objects by calling [self objects] My cellForItemAtIndex is a modified version that also returns the current object of an index. 我的后端数据存储在Parse.com上,我可以通过调用[self objects]访问当前加载的对象。cellForItemAtIndex是修改后的版本,还返回了索引的当前对象。

From what I understand in my cellForItemAtIndex I need to check for an image, if there isn't one I need to download one on background thread and set it so it shows in the cell, then store a copy of it in cache so that if the associated cell goes off screen when I do scroll back to it I can use the cached image rather than downloading it again. 根据我对cellForItemAtIndex的了解,我需要检查一幅图像,如果没有图像,则需要在后台线程上下载一个图像并将其设置为显示在单元格中,然后将其副本存储在缓存中,以便当我确实回滚到它时,相关的单元会从屏幕上消失,我可以使用缓存的图像,而不必再次下载它。

My custom parse collectionViewController gives me all the boiler plate code I need to get access to next set of objects, current loaded objects, pagination, pull to refresh etc. I really just need to get this collection view sorted. 我的自定义解析collectionViewController为我提供了访问下一组对象,当前加载的对象,分页,拉动刷新等所需的所有样板代码。我真的只需要对这个收集视图进行排序即可。 I never needed to do any of this with my tableview of a previous app which had much more images. 我从来不需要用以前的应用程序的tableview做任何事情,该应用程序具有更多的图像。 It's really frustrating spending a whole day trying to solve an issue and getting no where. 一整天试图解决问题却一无所获,真是令人沮丧。

This is my current collectionView cellForItemAtIndex: 这是我当前的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;

}

I am also using a custom collectionViewCell: 我也在使用自定义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

If there's any more information you'd like please ask. 如果您需要更多信息,请询问。 I'd just like a clear example in code of how to do this correctly, if it still doesn't work for me then I guess there is something wrong some where within my code. 我只是想在代码中清楚地说明如何正确执行此操作,如果它仍然对我不起作用,那么我猜代码中的某些地方出了问题。

I'm going to continue reading through various threads and resources I've come across in the last few days. 我将继续阅读过去几天遇到的各种线程和资源。 I can say one benefit in this experience is that I have a better understanding of threads and lazy loading but it is still very frustrated that I have made any progress with my actual app. 我可以说这种经验的好处是,我对线程和延迟加载有了更好的了解,但是对于我的实际应用程序取得了任何进展,仍然感到非常沮丧。

Incase you wondered here is my previous post: In a UICollectionView how can I preload data outside of the cellForItemAtIndexPath to use within it? 如果您想知道这里是我的上一篇文章: 在UICollectionView中,如何在cellForItemAtIndexPath外部预加载数据以在其中使用?

I'd either like to do this quick and manually or using the AFNetwork as that didn't cause any errors or need hacks like SDWebImage did. 我要么想快速手动地执行此操作,要么想使用AFNetwork,因为它不会引起任何错误或需要像SDWebImage这样的黑客。

Hope you can help 希望你能帮忙

Kind regards. 亲切的问候。

You can make use of the internal cache used by NSURLConnection for this. 您可以为此使用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;

}

This is for a table view, but same concept basically. 这是用于表格视图,但基本上是相同的概念。 I had the same issue you were having. 我遇到了同样的问题。 I had to check for a cached image, if not, retrieve it from a server. 我必须检查缓存的图像,如果没有,请从服务器检索它。 The main thing to watch out for is when you retrieve the image back, you have to update it in the collection view on the main thread. 需要注意的主要事情是,当您取回图像时,必须在主线程上的集合视图中对其进行更新。 You also want to check if the cell is still visible on the screen. 您还想检查该单元格是否仍在屏幕上可见。 Here is my code as an example. 这是我的代码作为示例。 teamMember is a dictionary and @"avatar" is the key which contains the URL of the user's image. teamMember是字典,@“ avatar”是包含用户图像URL的键。 TeamCommitsCell is my custom cell. 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"]];
                            }
                        });
                    }
                }
            });
        }
    }

NSURLCache is iOS's solution to caching retrieved data, including images. NSURLCache是​​iOS的用于缓存检索到的数据(包括图像)的解决方案。 In your AppDelegate, initialize the shared cache via: 在您的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];
}

Then use AFNetworking's UIImageView category to set the image using: 然后使用AFNetworking的UIImageView类别使用以下方法设置图像:

[imageView setImageWithURL:myImagesURL placeholderImage:nil];

This has proven to load images the second time around incredibly faster. 已经证明,第二次加载图像的速度非常快。 If you are worried about loading images faster for the first time, you will have to create a way to determine when and how many images you want to load ahead of time. 如果您担心第一次更快地加载图像,则必须创建一种方法来确定要提前加载的时间和数量。 It is very common to load data using paging. 使用分页加载数据非常普遍。 If you are using paging and still are having trouble, consider using AFNetworking's: 如果您正在使用分页并且仍然遇到问题,请考虑使用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;

This way you can create an array of UIImages and using this method to return the images for each cell before dequeuing the cell. 这样,您可以创建UIImages数组,并使用此方法在使单元出队之前返回每个单元的图像。 So in this case you would have two parallel arrays; 因此,在这种情况下,您将有两个并行数组; one holding your data and the other holding corresponding UIImages. 一个保存您的数据,另一个保存相应的UIImages。 Memory management will eventually get out of hand so keep that in mind. 内存管理最终将失去控制,因此请记住这一点。 If someone scrolls quickly to the bottom of the available cells, there is honestly not much else you can do since the data depends on the network connection of the user. 如果有人快速滚动到可用单元格的底部,则说实话,您无能为力,因为数据取决于用户的网络连接。

After several days the issue was my images were far too large. 几天后,问题是我的图像太大了。 I had to resize them and this instantly solved my issue. 我不得不调整它们的大小,这立即解决了我的问题。

I literally narrowed things down and checked my images to find they were not being resized by the method I thought was resizing them. 我从字面上缩小了范围,并检查了我的图像,发现它们没有按照我认为调整其大小的方法进行调整。 This is why I need to get myself used to testing. 这就是为什么我需要习惯于测试。

I learnt a lot about GCD and caching in the past few days but this issue could have been solved much earlier. 在过去的几天里,我学到了很多有关GCD和缓存的知识,但是这个问题早就可以解决了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM