在 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】:

我建议您使用AFNetworkingUIImageView+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 单元格高度

如何在 UICollectionViewSectionHeader 中嵌入 UICollectionView

如何在 UICollectionView 中快速添加分页?

如何在 UICollectionView 中强制重新加载自定义单元格?