UICollectionView 在滚动时重新加载错误的图像

Posted

技术标签:

【中文标题】UICollectionView 在滚动时重新加载错误的图像【英文标题】:UICollectionView reloads wrong images on scroll 【发布时间】:2014-02-19 15:25:57 【问题描述】:

在下面的代码中,我从文本文件下载图像 URL,将其存储在 NSMutableArray 中,然后从 CellforItemAtIndexPath 中的这些 URL 下载图像。但是,当我上下滚动片刻时,错误的图像出现在错误的位置。一秒钟后它会更正,但我想摆脱那个滚动问题。代码如下:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
    static NSString *identifier = @"Cell2";

    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
    AsyncImageView *recipeImageView = (AsyncImageView *)[cell viewWithTag:100];
    UILabel *titleView = (UILabel *)[cell viewWithTag:101];
    titleView.numberOfLines = 3;
    UIImageView *overlay = (UIImageView *)[cell viewWithTag:111];
    FXBlurView *blurView = (FXBlurView *)[cell viewWithTag:110];
    blurView.tintColor = [UIColor colorWithRed:51.0 green:51.0 blue:51.0 alpha:1];


    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^
        hi = [self videoTitle];
        images = [self firstPhoto];
        // switch to a background thread and perform your expensive operation


        // switch back to the main thread to update your UI


        dispatch_async(dispatch_get_main_queue(), ^
            NSString *imgSrc;

            NSString *url = images[indexPath.row];
            imgSrc = url;
            if (imgSrc != nil && [imgSrc length] != 0 ) 
                [recipeImageView setImageWithURL:[NSURL URLWithString:images[indexPath.row]] placeholderImage:[UIImage imageNamed:@"red.png"]];

            
            else 
                NSLog(@"noimage");
                recipeImageView.image = [UIImage imageNamed:@"red.png"];
            
            titleView.text = hi[indexPath.row];


        );
    );

    return cell;


    // recipeImageView.image = [UIImage imageNamed:[videoList objectAtIndex:indexPath.row]];


有什么想法吗?谢谢。

【问题讨论】:

【参考方案1】:

我不确定AsyncImageView是什么,所以首先让我像UIImageView一样回答你的问题:


在您的回调块中(当您返回主队列时),您指的是recipeImageView。但是,如果用户在下载过程中进行了滚动,则 cell 对象已被重用。

一旦您进入回调,您必须假设您对视图的所有引用(cellrecipeImageViewblurView 等都指向错误的东西。您需要 使用您的数据模型找到正确的视图。

一种常见的方法是使用 NSIndexPath 来查找单元格:

UICollectionViewCell *correctCell = [collectionView cellForItemAtIndexPath:indexPath]:
UIImageView *correctRecipeImageView = (UIImageView *)[correctCell viewWithTag:100];
/* Update your image now… */

请注意,这只有在一个indexPath 始终保证指向相同的内容时才有效 - 如果用户可以插入/删除行,那么您也需要找出正确的 indexPath,因为它可能有也改变了:

NSIndexPath *correctIndexPath  = /* Use your data model to find the correct index path */
UICollectionViewCell *correctCell = [collectionView cellForItemAtIndexPath:correctIndexPath]:
UIImageView *correctRecipeImageView = (UIImageView *)[correctCell viewWithTag:100];
/* Update your image now… */

也就是说,如果 AsyncImageView 是应该为您处理所有这些的其他类,那么您可以在主线程上调用 setImageWithURL: placeholderImage:,它应该代表您处理所有异步加载。如果没有,我想推荐 SDWebImage 库,它可以:https://github.com/rs/SDWebImage

【讨论】:

那应该是这样吗? pastie.org/8751762。这个不行,还是一样的问题(我只是做了个UIImageView)。 SDWebImage 可以更好地解决滚动问题。谢谢@AaronBrager 永远不要使用viewWithTag,它太脆弱了。至少当你的单元格可以有一个 @IBOutlet 时不会。 @DepartamentoB 您是正确的,子类化和属性是更好的解决方案,但在这种情况下,OP 不是子类化 UICollectionViewCell,我发布的解决方案是使其工作所需的最小更改。我不认为我同意你应该“永远”使用这种技术【参考方案2】:

我注意到一堆没有 cmets 的反对票,在再次阅读我的答案后,我猜最初接受的答案(在行下方)可能会更好。

这类问题会发生一些事情。 UITableView 只创建足够的 UITableViewCells 来显示视图中的每个单元格以及将滚动到视图中的内容。其他单元格在滚动出视图后将被重新使用。

这意味着当您快速滚动时,单元格会设置多次以异步获取图像然后显示它,因为它获取图像的速度非常慢。图像加载后,它将显示在最初调用来获取它的单元格上,但该单元格可能已经被重复使用。

现在我有一个缓冲图像:UIImage?我用作 UITableViewCells 数据的任何数据类的属性。这最初总是为零,所以我异步获取图像,当 dispatch_async 返回时它将存储在那里,所以下次我需要显示这个单元格时它已经准备好了,如果它仍然是同一个单元格,我也会在单元格中显示它.

所以正确的做法是:

开始使用您的数据对象配置单元格 将数据对象存储在单元格中,以便以后引用 检查数据对象是否已经在 bufferedImage 中设置了图像,如果是,则显示它,就是这样 如果还没有图像,请将当前图像重置为空或占位符,然后开始在后台下载图像 当异步调用返回时,将图像存储在 bufferedImage 属性中 检查存储在当前单元格中的对象是否仍然是您为其提取图像的对象(这非常重要),如果不是,则不执行任何操作 如果您仍在同一个单元格中,请显示图片

因此,您在异步调用中传递了一个对象,以便您可以存储获取的图像。您可以比较当前单元格中的另一个对象。通常,在您获取图像之前,单元格已经被重复使用,因此这些对象在您下载图像时会有所不同。


滚动和旋转 CollectionViews 总是有点问题。我总是有同样的问题,图像出现在错误的单元格中。对影响图像但不影响标签的 collectionview 进行了某种优化。对我来说这是一个错误,但在三个 ios 版本之后仍未解决。

您需要实现以下内容:

https://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionViewLayout_class/Reference/Reference.html#//apple_ref/occ/instm/UICollectionViewLayout/shouldInvalidateLayoutForBoundsChange:

这应该始终返回 YES 以始终刷新它。如果这解决了您的问题,那么您可以考虑不每次都执行整个重绘,因为这对性能不利。我个人花了很多时间来计算屏幕是否通过或线路是否通过等等,但它变得结结巴巴,所以我最终只返回“是”。 YMMV。

【讨论】:

【参考方案3】:

我看到您已经回答了这个问题,但仍然想分享我的经验。

我遇到了同样的问题。

就我而言,这个问题的原因是以下函数:collectionView.dequeueReusableCell(...)。

每个案例的单元格类型都相同,但数据不同:

在某些情况下,特定单元格的数据包含图像。 在某些情况下,特定单元格的数据不包含图像,它是 nil。

当图像为零时,我没有将图像设置到 imageView。在这种情况下,集合视图会重复使用其他单元格的图像(当前单元格的图像错误)。

解决方法很简单:如果imageView不包含图片,就设置nil。

希望这会对你有所帮助。

【讨论】:

以上是关于UICollectionView 在滚动时重新加载错误的图像的主要内容,如果未能解决你的问题,请参考以下文章

UICollectionView在滚动时重新加载错误的图像

UICollectionView 单元格在可见时不断重新加载

在 UICollectionView 中重新加载数据后保持 collectionview 速度

iOS 9 UICollectionView 水平重新加载数据不起作用

UICollectionView insertItemsAtIndexPaths:似乎重新加载collectionview或自动滚动到顶部

UICollectionViewFlowLayout 子类在滚动和重新加载时崩溃访问超出范围的数组