UITableView 中多张图片的延迟加载

Posted

技术标签:

【中文标题】UITableView 中多张图片的延迟加载【英文标题】:Lazy Loading of several images in UITableView 【发布时间】:2012-07-20 16:09:50 【问题描述】:

下载多个图像并将每个图像加载到 UITableViewCell 内的 UIImageView 中的最佳做法是什么?特别是,我应该在下载后调整/替换 UIImageview 的大小还是应该调整图像的大小以适合 UIImageView。请注意,调整/替换 UIImageView 也会调整/替换 UITableViewCell。它会引起任何问题吗?

【问题讨论】:

【参考方案1】:

一些想法:

下载多个图像并将每个图像加载到UIImageView(位于UITableViewCell 中)的最佳做法是什么?

有关下载图像的最佳做法包括:

    一定要利用延迟加载(根据需要而不是之前加载图像)。

    异步下载图片。

    确保您的下载技术将取消对不再可见的表格视图单元格的请求。例如,如果您的网络速度较慢并在表格视图中快速向下滚动,您不希望占用您的设备下载不再可见的图像。

    确保不要对服务器发出过多的并发请求。在 ios 中,当您超过 5 或 6 个并发请求时,后续请求将冻结,直到前面的请求完成。在最坏的情况下,后续请求实际上会因为超时而开始失败。

    缓存您的结果。至少,将它们缓存在内存中。您可能还希望将它们缓存到持久存储(也称为“磁盘”)。

如果您要为异步操作、缓存等编写自己的代码,您可能希望使用 NSOperationQueue 而不是 GCD,以便我可以约束 number of background requests 并发出请求 cancelable。您将使用NSCache 来缓存图像。您可能会使用UITableViewCell 子类(或类别),以便您可以保存weak 对“先前”操作的引用,以便您可以取消任何不完整的先前请求。

如您所见,这很重要,我建议您使用现有的UIImageView 类别,例如作为SDWebImageAFNetworking 的一部分提供的类别。恕我直言,前者更丰富一些(例如,提供磁盘缓存),但如果您正在做大量网络工作并想坚持使用单一框架,AFNetworking 也可以做得很好。

稍后你问:

特别是,我应该在下载后调整/替换 UIImageview 的大小,还是应该调整图像的大小以适应 UIImageView。请注意,调整/替换 UIImageView 也会调整/替换 UITableViewCell。会不会有什么问题?

    如果您的图像大于单元格的缩略图视图所需的大小,您有两种方法。首先,您可以使用UIViewContentModeScaleAspectFit or UIViewContentModeScaleAspectFill 中的contentMode(如果您使用AspectFill,请确保您还将clipsToBounds 设置为YES)。更棒的是,你下载后还可以resize the image。

    这是个人意见,但我认为在单元格上有一个固定大小的UIImageView,然后当异步图像下载完成后,只需设置@987654351 的image 属性。 @。您希望图像在下载时优雅地出现在您的 UI 中,但您通常不希望在用户已经在阅读那里的内容时对视图进行不和谐的重新布局。如果您的设计绝对需要重新布局单元格,那么您可以致电reloadRowsAtIndexPaths

【讨论】:

@Yanchi 虽然感谢您的支持,但我必须承认,我随后了解到我近一年前发布的那个简单的 GCD 实现存在重大限制。至少,我建议NSOperationQueueNSCache。更好的是,使用现有的 UIImageView 类别(SDWebImage 或 AFNetworking 的)来解决我在修改后的答案中列举的许多问题。 我尝试使用SDWebImage,但由于缺少某些头文件而无法编译,但感谢您的新答案! @Yanchi 我刚刚提取了最新的SDWebImage,现在对第三方库libwebp 的依赖无济于事,如果没有,你会得到一个神秘的@ 987654358@ 错误。我有posted an issue,所以希望他们能尽快解决这个问题。如果没有,我在他们引入这种不必要的依赖之前发布了关于如何返回到版本的评论。 @Yanchi 响应issue I posted,他们已经解决了这个问题。如果你提取最新的SDWebImage,它现在应该可以像宣传的那样工作,没有任何关于缺少标题的奇怪错误。 干得好 Rob :) 我不敢写问题,因为我不确定问题是否不在我这边 :) 由于我们必须在修复之前提交应用程序,所以我使用了同步请求在 dispatch_async 块中.. 下载的图像使用 indexPath.row 键保存在字典中,因此它们不会重复,您可以从此缓存中获取图像而不是再次下载它们.. 我想知道苹果是否会接受它,因为有时应用程序似乎创建很多线程(比如 50,如果你真的快速向下滚动)【参考方案2】:

UITableViewCell 中延迟加载图像的常见做法是使用通知回调让UITableViewCell 知道何时收到图像。

本质上,您需要创建一个 UIImageView 的子类,其中包含一个 imageURL 字段,该字段在更改时会触发对图像的请求,并使用它而不是标准的 UIImageView

UIImageView 子类的接口:

@property (nonatomic, copy) NSString *imageURL;

UIImageView 子类的实现:

//synthesize property
@synthesize imageURL = _imageURL;

- (void)setImageURL:(NSString *)imageURL 
    if(_imageURL)
        [[NSNotificationCenter defaultCenter] removeObserver:self name:_imageURL object:nil];
    _imageURL = [imageURL copy];
    //if imageURL is valid...
    if(_imageURL.length) 
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveImage:) name:_imageURL object:nil];
        //fire off some asynchronous image fetch
        //when the image fetch completes, sent off a notification using the imageURL as the notification name
        //It's up to you to create the implementation for this yourself
        ... [MyImageManager fetchImage:_imageURL notificationName:_imageURL]; 
    


- (void)didReceiveImage:(NSNotification*)notification

    //handle your received image here
    if([notification.object isKindOfClass:[UIImage class]])
    
        self.myCustomImageView.image = notification.object;
    

然后在 UITableViewCell 类中重写prepareForReuse时:

- (void)prepareForReuse 
    [super prepareForReuse];
    self.myCustomImageView.imageURL = nil;
    self.myCustomImageView.image = nil;
    //do the rest of your prepareForReuse here:
    ...

现在,就图像的整体大小调整与图像视图的大小调整而言,您应该做的是不理会 imageView 的大小,并使用contentMode 属性来处理生成图像的不同大小.您的possible values 是:

UIViewContentModeScaleToFill,
UIViewContentModeScaleAspectFit,
UIViewContentModeScaleAspectFill,
UIViewContentModeRedraw,
UIViewContentModeCenter,
UIViewContentModeTop,
UIViewContentModeBottom,
UIViewContentModeLeft,
UIViewContentModeRight,
UIViewContentModeTopLeft,
UIViewContentModeTopRight,
UIViewContentModeBottomLeft,
UIViewContentModeBottomRight,

每个都有各自的结果 - 您可能想要使用 UIViewContentModeScaleAspectFit,因为这将重新调整图像大小以适合 imageView,而不会扭曲它 - 这可能会在大小或顶部/底部留下空白边距的图像视图。 Aspect fill 会做类似的事情,只是它将图像调整为足够大以填充整个 imageView,并且可能会切断图像的侧面或顶部/底部。 Scale to fill 只会拉伸图像以填充 imageView。

【讨论】:

所以你是说每当我得到图像时调整每个单元格的大小是否正常? 实际设置图片时,不需要调整UIImageView的大小。你可以做的是设置imageView的contentMode。我将编辑我的答案以反映这一点。 正如您在最后一段中指出的那样,如果我不调整图像视图的大小,它可能会留下一些边距,这是不可取的。 如果您不愿意缩放图像,那您为什么要问这个?您有两个选择 - 调整 imageView 的大小,或调整图像的大小。如果您拒绝缩放图像,那么您必须调整 imageView 的大小(可能还有它的单元格)。 You do not need to reload the entire table - 你可以用新的高度重新加载单元格。

以上是关于UITableView 中多张图片的延迟加载的主要内容,如果未能解决你的问题,请参考以下文章

UITableView 延迟加载问题

使用 Alamofire 在 UITableview 中延迟加载 API 请求

在具有滚动索引的 UITableView 中延迟加载图像

延迟加载 - UITableview 可见单元格不显示图像

如何在 UITableView 中高效加载图片?

如何在 UITableView 中实现类似 UIImageView 的延迟加载