UITableView使重新加载旧图像的单元格出列

时间:2013-05-02 03:04:15

标签: ios objective-c uitableview

我知道这已被问过很多次,但请耐心等待,因为我已经尝试了很多时间来寻找解决方案。

我有一个rss解析器,一个tableview,其中:text,detail和images设置为AccessoryDisclosureIndicator.view。

图像加载非常简单的GCD异步:快速滚动数百个结果。没有延迟,如果我有良好的连接就没有错误。

问题是,一瞬间 - 它们会在负载上闪烁,因为该单元正在被重用。此外,如果连接不好,它有时会留下散乱的图像但文本/细节是正确的,只有图像是旧的...所以让我重复一遍,文本/细节更新正常,永远不会重新排队错误,只是图像有时很少在错误的连接上排队错误/癫痫发作 - 快速来回滚动。

我的问题,有人可以帮我缓存/标记cell.accs.views吗?我尝试设置cellID但是我的实现有问题。如果连接从不减慢,我的下面的代码工作得很好,重新排队一个我不介意的单元格的轻微闪烁。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];

    [cell.textLabel setNumberOfLines:3];
    cell.textLabel.font = [UIFont boldSystemFontOfSize:14.0];

    [cell.detailTextLabel setNumberOfLines:3];
    cell.detailTextLabel.font = [UIFont systemFontOfSize:12.0];
    cell.detailTextLabel.textColor = [UIColor blackColor];
}

RSSItem * rssItem = (RSSItem *)(_rssParser.rssItems)[indexPath.row];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,  0ul);
dispatch_async(queue, ^{
    //This is what you will load lazily
    NSData   *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:rssItem.imageURL]];
    dispatch_sync(dispatch_get_main_queue(), ^{

        UIImageView *accImageView = [[UIImageView alloc] initWithImage:[UIImage imageWithData:data ]];

        [accImageView setFrame:CGRectMake(0, 0, accImageView.image.size.width, 92)];
        cell.accessoryView = accImageView;

        //[cell setNeedsLayout];//not needed
    });
});

[cell.textLabel setText:rssItem.title];
[cell.detailTextLabel setText:rssItem.summary];

return cell;}

4 个答案:

答案 0 :(得分:10)

关键问题是,在您dispatch_async获取新图像(您知道可能需要一些时间,甚至连接速度慢几秒)之前,请重置旧图像或使用占位符图像加载它。

两个次要问题:

  1. 请注意,如果使用速度非常慢的网络,那么在图像恢复时,您可以将单元格滚动到屏幕上(例如,想象一下,您向上轻拂了滚动视图,然后,当图像仍然被加载时,你将它向后翻了下来)。在尝试设置图像之前,您确实应该确保单元格仍然可见。

    您可以通过检查(在主队列上)单元格是否可见来执行此操作:

    dispatch_async(dispatch_get_main_queue(), ^{
        UITableViewCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
        if (updateCell != nil) {
            // update image in `updateCell`
        }
    });
    

    请注意,UITableView方法cellForRowAtIndexPath不应与UITableViewDataSource方法tableView:cellForRowAtIndexPath:混淆。前者只是检查单元格是否可见,如果是,则返回指向它的指针。显然,后者是您在问题中引用的方法,用于创建和配置表格视图单元格。

    另请注意,您使用NSIndexPath检查单元格是否仍然可见的这个想法假定在中间时间内表格视图中可能没有插入其他行。这通常不是一个有效的假设,因此您甚至可能必须返回基础模型并重新计算此单元格的indexPath,然后再继续上述逻辑以查看该单元格是否仍然可见。

  2. 您可能不想使用全局队列,因为在网络非常慢的情况下,您的请求可能会开始积压并且您可能最终会遇到大量并发网络请求。这是一个问题,因为(a)系统一次不能执行超过4或5个并发请求; (b)你将耗尽iOS提供的有限数量的工作线程。你真的不应该有超过4或5个并发请求(如果你有更多,你不仅会看到没有性能提升,但它们实际上会阻止并最终超时)。我建议您考虑使用NSOperationQueuemaxConcurrentOperationCount设置为四个或五个。

    此外,这提供了一个最终优化,特别是使这些NSOperation子类请求可取消(并且显然,使取消逻辑实际上取消其中包含的网络请求)。因此,如果您快速滚动到表格中的第100行,您实际上不希望让可见单元格等待先前99行的图像下载完成。如果取消滚动屏幕的单元格的图像请求,则可以解决此问题。

  3. 如果您想要在非常慢的网络连接上诊断应用程序的行为,我建议您使用网络调节器,它可以让您的Mac模拟从模拟器上的全速到质量非常差的边缘网络。 。在Xcode的Xcode菜单中,选择“Open Developer Tool ...”,然后选择“More Developer Tools”。登录后,您将看到“Xcode的硬件IO工具”选项。网络调节器工具就在那里。您可以在最坏的情况下测试您的应用程序,这可能会让您不仅考虑我上面列出的两个问题,而且还考虑更高级的功能,如图像缓存等。

    你真的想要将图像缓存到持久存储,理想情况下是RAM和持久存储。有UIImageView个类别可以解决上述异步问题,并提供一些缓存:您可以考虑的两个类别是SDWebImageAFNetworking

答案 1 :(得分:1)

重新使用单元格时,您只需要清除旧图像。

最后,你可以添加:

cell.accessoryView = nil;

答案 2 :(得分:1)

  

问题是,一瞬间 - 它们在负载上闪烁,因为该单元正在被重用

但那就是你的答案。该单元格正在被重用,我在代码中看不到任何删除图像的内容。如果图像不是该行的正确图像(因为它在不同的行中重复使用),则需要将cell.accessoryView设置为nil(或者设置为带有占位符图像的图像视图) now < / em>的。正确的图像不会出现,直到以后dispatch_async)。

但是,您使用的代码存在严重缺陷,因为即使我们已经看过此行并因此已经下载了图像,它每次都会从Internet 获取图像 。我的书有更好的代码,用于在表格视图中加载图像,必要时下载它们并在此后存储它们。它还向您展示了如果在图像到达之前将行从表中滚动,如何停止下载,从而节省带宽:

http://www.apeth.com/iOSBook/ch37.html#_http_requests

向下滚动到它所说的位置&#34;例如,假设您需要下载图像以在UITableView的单元格中用作缩略图。让我们考虑如何根据需要懒洋洋地提供这些图像。&#34;

答案 3 :(得分:0)

以下是我使用SDWebImage做的事情,我被迫为所有单元格使用占位符,我只是将没有图像的那些设置为一个小的V形图标。按预期工作。谢谢大家。

RSSItem * rssItem = (RSSItem *)(_rssParser.rssItems)[indexPath.row];

if (rssItem.imageURL == nil ){
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
    [imageView setImageWithURL:[NSURL URLWithString:rssItem.imageURL] placeholderImage:[UIImage imageNamed:@"chevron.png"]];
    cell.accessoryView = imageView;
}else{
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 110, 96)];
    imageView.contentMode  = UIViewContentModeScaleAspectFit;
    [imageView setImageWithURL:[NSURL URLWithString:rssItem.imageURL] placeholderImage:[UIImage imageNamed:@"loading.png"]];
    cell.accessoryView = imageView;
}

[cell.textLabel setText:rssItem.title];
[cell.detailTextLabel setText:rssItem.summary];

return cell;