【问题标题】:iPhone - Asynchronous image only displays on scrolliPhone - 异步图像仅在滚动时显示
【发布时间】:2012-08-16 03:01:49
【问题描述】:

我一直在这个问题上撞墙并四处寻找无济于事的解决方案:

我从网络上提取了大量数据,我正在使用 Loren Brichter 的 ABTableViewCell 通过在每个单元格的 contentView 内绘制所有内容来使其平稳运行,以避免 UILabel 和 UIImageViews 减慢滚动速度。

这非常适合显示文本,但我遇到了图像问题,因为下载它们需要时间。一旦下载了相应的图像,我似乎无法找到一种方法来强制显示的每个单元格的 contentView 重新绘制自身。我必须指出我不是在绘制标签和图像视图,而只是为了节省内存而绘制 contentView。

现在表格的行为如下:

  • 加载:显示文本,无图像
  • 向上或向下滚动:最终图像 一旦单元格移出屏幕就会显示

一个示例项目是here

代码:

ABTableViewCell.h

@interface ABTableViewCell : UITableViewCell
{
    UIView *contentView;
}

ABTableViewCell.m

- (void)setNeedsDisplay
{
    [contentView setNeedsDisplay];
    [super setNeedsDisplay];

}

- (void)drawContentView:(CGRect)r
{
    // subclasses implement this
}

TableCellLayout.h

#import "ABTableViewCell.h"

@interface TableCellLayout : ABTableViewCell {

}

@property (nonatomic, copy) UIImage *cellImage;
@property (nonatomic, copy) NSString *cellName;

TableCellLayout.m

#import "TableCellLayout.h"

@implementation TableCellLayout

@synthesize cellImage, cellName;

- (void)setCellName:(NSString *)s
{
    cellName = [s copy];
    [self setNeedsDisplay];
}

- (void)setCellImage:(UIImage *)s
{
    cellImage = [s copy];
    [self setNeedsDisplay];
}

- (void)drawContentView:(CGRect)r
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextFillRect(context, r);

    [cellImage drawAtPoint:p];
    [cellName drawAtPoint:p withFont:cellFont];
}

TableViewController.m

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

    TableCellLayout *cell = (TableCellLayout *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if(cell == nil)
    {
        cell = [[TableCellLayout alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }

    cell.cellName = [[array valueForKey:@"name"] objectAtIndex:indexPath.row];
    cell.cellImage = [UIImage imageNamed:@"placeholder.png"]; // add a placeholder    

    NSString *imageURL = [[array valueForKey:@"imageLink"] objectAtIndex:indexPath.row];
    NSURL *theURL = [NSURL URLWithString:imageURL];

    if (asynchronousImageLoader == nil){
        asynchronousImageLoader = [[AsynchronousImages alloc] init];
    }
       [asynchronousImageLoader loadImageFromURL:theURL];

        cell.cellImage = asynchronousImageLoader.image;

return cell;
}

这是图像准备好后 AsynchronousImageLoader 调用的最后一个方法:

- (void)setupImage:(UIImage*)thumbnail {
    self.image = thumbnail;
        [self setNeedsLayout];

}

我只需要正确的方法来告诉我的可见单元格在下载行的图像后重新绘制自己。我想我应该在最终方法(setupImage)中添加一些东西——但我似乎无法让它按应有的方式工作。想法?非常感谢!

最终编辑:解决方案

没错,正如所怀疑的那样,问题在于调用完成后,没有告知可见单元格重绘并更新为下载的图像。

我使用以下答案提供的帮助来制定一个适合我需求的解决方案:

在异步图片下载器调用的final方法中添加回调:

AsyncImageView.m

 - (void)setupImage:(UIImage*)thumbnail {

      self.cellImage = thumbnail;
[cell setNeedsDisplay];

}

注意:我还在图像下载器类的初始化中设置了一个本地占位符图像,只是为了美化一点。

然后在我的 cellForRowAtIndexPath 中:

NSURL *theURL = [NSURL URLWithString:imageURL];

AsyncImageView *aSync = [[AsyncImageView alloc] initWithFrame:CGRectMake(0, 0, 20, cell.bounds.size.height)];

[aSync loadImageFromURL:theURL];
cell.cellImageView = aSync;

return cell;

可能还有一两个其他调整,但这些是这里的主要问题。再次感谢 SO 社区!

【问题讨论】:

  • 你解决了吗?我在两天前用我测试过的代码更新了我的答案。请您检查详细信息并确认已解决。谢谢!
  • 嗨,是的,社区再次变得很棒。我会接受答案并写一个简短的编辑来展示我做了什么来让这一切正常工作。感谢您的帮助!
  • 很高兴听到,乐于助人!

标签: iphone objective-c asynchronous uitableview custom-cell


【解决方案1】:

确保您在主线程中更新单元格图像。 UI 更新仅在此处完成后才会显示,这就是为什么您只能在触摸和滚动时看到更新的原因。

if(cell) {

    dispatch_async(dispatch_get_main_queue(), ^{

        cell.cellImage = thumbnail;
        [cell setNeedsDisplay];

    });

}

[编辑]

您需要将单元格作为图像加载器的代理并拥有异步重绘机制。

......
    AsynchronousImages *asynchronousImageLoader = [[AsynchronousImages alloc] init];
    asynchronousImageLoader.delegate = cell;
    [asynchronousImageLoader loadImageFromURL:theURL];

    return cell;
}

并将委托回调代码放在单元实现中。

- (void)setupImage:(UIImage*)thumbnail {

      self.cellImage = thumbnail;

}

【讨论】:

  • 嘿沃伦,我已经尝试过一点,但没有得到任何工作。我在主线程上运行(我从不切换到后台)所以不是这样。我有uploaded 一个准系统示例项目,如果您愿意花几分钟时间浏览一下,我将永远感激不尽。
  • 这里的实际问题是你在这里所拥有的永远不会工作。你不应该调用 cellForRowAtIndexPath: 并期望得到当前显示的单元格。见编辑
【解决方案2】:

您可以使用 Apple TableView 延迟加载。他们有异步下载图像的示例代码。见下方链接

Apple LazyTableImages

在 AsynchronousImages 类中,您可以添加一个属性 NSIndexPath 并且 AsynchronousImages 上的委托应该更改。请看下面的代码

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

    TableCellLayout *cell = (TableCellLayout *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if(cell == nil)
    {
        cell = [[TableCellLayout alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }

    cell.cellName = [[array valueForKey:@"name"] objectAtIndex:indexPath.row];
    cell.cellImage = [UIImage imageNamed:@"placeholder.png"]; // add a placeholder    

    NSString *imageURL = [[array valueForKey:@"imageLink"] objectAtIndex:indexPath.row];
    NSURL *theURL = [NSURL URLWithString:imageURL];


    AsynchronousImages *asynchronousImageLoader = [[AsynchronousImages alloc] init];
    asynchronousImageLoader.indexPath = indexPath;
    [asynchronousImageLoader loadImageFromURL:theURL];
    return cell;
}

//Delegate should be
- (void)setupImage:(UIImage*)thumbnail index:(NSIndexPath*) indePath {
    TableCellLayout *cell = (TableCellLayout *) [tableView cellForRowAtIndexPath:indexPath];
    if(cell) {
      cell.cellImage = thumbnail;
      [cell setNeedsDisplay];
    }
}

【讨论】:

  • 我知道苹果的例子,但我现在一切正常。我真的认为这只是在图像完成下载后能够强制可见单元格重绘的问题。您对如何实现这一目标有任何想法吗?感谢您的回复!
  • 但您的代码很复杂,因为当委托 setupImage 触发时,您无法获得单元格的正确行来显示图像。您可以查看苹果示例代码以了解他们是如何制作的。
  • 你没有released(或autoreleasedasynchronousImageLoader
【解决方案3】:

验证 UIImage 的创建和 UIImageView 的图像属性的设置发生在主线程上。没有理由设置图像视图的图像不应该使其 rect 可见。

如果您正在重复使用单元格,请确认您的加载已正确取消。

【讨论】:

  • 嗨,贾斯汀,谢谢。最后帮助我指出了正确的方向!
【解决方案4】:

使用AsyncImageView 代替uiimageview

【讨论】:

  • Lithu,我正在使用 AsyncImageView。图像已下载,但在可见单元格移出屏幕(然后重新打开)之前不会向用户显示。我猜这是手动强制重绘单元格的问题。想法?
  • chk 源代码附带的示例教程..它很好地解释了如何使其正确运行。在 tableview cellforRow 方法中,设置 asynchimage 变量的 imageurl 属性..它对我来说很好..希望它有帮助
  • tht 类不仅提供类别,还提供 uiimageview 的 asyncimageview 子类
  • 如果您可以保存数据,您可以在我认为的 customcell drawrect 方法中加载它
【解决方案5】:

您可以将您的 tableView 告诉 reloadData... 或者更精细的重新加载:

  [self.tableView reloadRowsAtIndexPaths:self.tableView.indexPathsForVisibleRows withRowAnimation:UITableViewRowAnimationNone];

编辑添加(查看 OP 的源代码后):

我查看了您的项目,问题出在您的架构上。你在滥用AsyncImageView,因为你只是用它来异步加载你的图像——而它的设计目的是同时加载、和显示图像。这就是为什么它没有“回调”功能来让您知道何时检索到数据。

您最好将CellLayout 的图像属性替换为 UIImageView 属性。 (请注意,UIImageView 在绘图方面比image drawAtPoint 更有效)。

所以:

  1. CellLayout 类更改为使用UIImageView 属性而不是UIImage
  2. 更改您的 cellForRowAtIndexPath 以将 AsyncImageView 设置为直接在您的 Cell 上的属性。

如果您想支持占位符,则应将其添加到您的 AsyncImageView 类中 - 以便它知道在下载内容时要显示什么。

cellForRowAtIndexPath:

NSURL *theURL = [NSURL URLWithString:imageURL];

AsyncImageView *aSync = [[AsyncImageView alloc] initWithFrame:CGRectMake(0, 0, 20, cell.bounds.size.height)];

[aSync loadImageFromURL:theURL];
cell.cellImageView = aSync;

return cell;

CellLayout.h

@property (nonatomic, strong) UIImageView *cellImageView;

CellLayout.m

- (void)setCellImageView:(UIImageView *)s
{
    [cellImageView removeFromSuperview];
    cellImageView = s;
    [self addSubview:cellImageView];
}

【讨论】:

  • 嗯,我已经浏览了答案中列出的所有内容,但无法解决这个问题。我知道我才刚刚开始,所以我可能错过了一些愚蠢的东西。我整理了一个样本project。您可能很忙,但如果您有几分钟的空闲时间,如果您能帮助结束这个谜团,我将非常感激!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多