【问题标题】:How to get UITableViewCell images to update to downloaded images without having to scroll UITableView如何让 UITableViewCell 图像更新为下载的图像而无需滚动 UITableView
【发布时间】:2011-10-24 10:50:58
【问题描述】:

我正在尝试使用我自己的惯用 UITableView + 异步下载 + 缓存技术。我正在做的是,对于在 cellForRowAtIndexPath 中出列的每个单元格:

1-Check if it's corresponding thumbnail image is already 'cached' in /Library/Caches
2-If it is, just use that.
3-If not, load a default image and enqueue an NSInvocationOperation to take care of it:
   4a-The NSInvocationOperation gets the image from a remote server
   4b-Does the UIGraphicsBeginContext thing to scale down the image to 40x40  
   4c-saves the scaled down version to /Library/Cache
   4d-'SHOULD' update the cell's image to the new downloaded and downsized image, if the cell is still visible. 

但是,我不知道如何让单元格更新它们的图像,除非我手动滚动它们并返回屏幕。我唯一能做的就是让 NSOperation 在完成后通过 performSelectorOnMainThread 调用主线程,然后主线程可以调用 [viewtable reloadData]。但这似乎很浪费:每次单元格的新图像准备好时,我都会重新加载整个表格。

作为一种不那么浪费的方法,我让主线程设置了一个 bool 标志,然后在 scrollViewDidEndDecelerating 时,如果设置了该标志,则调用 [viewtable reloadData]。使用这种方法,单元格仅在用户完成滚动时刷新。

但是,我仍然希望只更新可见单元格,如果它们的缓存图像在它们仍然可见的情况下已准备好(这意味着用户没有将它们滚动到视图之外)。

到目前为止,这是我的代码:

 - (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] autorelease];

    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    cell.selectionStyle = UITableViewCellSelectionStyleGray;
  }

  // Configure the cell...
  cell.textLabel.text = [[dbData objectAtIndex:indexPath.row] objectAtIndex:0];
  cell.detailTextLabel.text = [[dbData objectAtIndex:indexPath.row] objectAtIndex:1];

  NSString *ImageName = [[dbData objectAtIndex:indexPath.row] objectAtIndex:2];
  NSString *cachedImageName = [[[ImageName stringByDeletingPathExtension] stringByAppendingString:thumbnailSizeSuffix] stringByAppendingPathExtension:@"png"];
  NSString *cachedImagePath = [cachePath stringByAppendingPathComponent:cachedImageName];

  if([[NSFileManager defaultManager] fileExistsAtPath:cachedImagePath])
    cell.imageView.image = [UIImage imageWithContentsOfFile:cachedImagePath];
  else
  {
    cell.imageView.image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:defaultNeedsDownloadIconFile ofType:@"png"]];
    NSArray *package = [NSArray arrayWithObjects:ImageName, cachedImagePath ,referencingTable, nil];                                    
    NSInvocationOperation *concurrentImageLoader = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadURI:) object:package];
    [concurrentQueue addOperation: concurrentImageLoader];
    [concurrentImageLoader release];
  }

  return cell;
}

对于 NSInvocationOperation 的“内核”,我试过这个:

- (void)loadURI:(id)package
{
  NSArray *payload = (NSArray*)package;

  NSString *imageName = [payload objectAtIndex:0];
  NSString *cachedImagePath = [payload objectAtIndex:1];
  NSString *imageURL = [NSString stringWithFormat:@"http://www.useanddisposeof.com/VentanaSurDB/%@/photo/%@",[payload objectAtIndex:2], imageName]; 

  UIImage *newThumbnail = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];

  if(!newThumbnail)
    newThumbnail = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:defaultNotFoundIconFile ofType:@"png"]];

  UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, thumbnailSize.width, thumbnailSize.height)];
  imageView.layer.borderColor = [UIColor blackColor].CGColor;
  imageView.layer.cornerRadius = 4.0;
  imageView.layer.masksToBounds = YES;
  imageView.layer.borderWidth = 1.0;
  imageView.image = newThumbnail;

  UIGraphicsBeginImageContext(CGSizeMake(thumbnailSize.width, thumbnailSize.height));
    [imageView.layer renderInContext:UIGraphicsGetCurrentContext()];
    newThumbnail = UIGraphicsGetImageFromCurrentImageContext();      
  UIGraphicsEndImageContext();

  [imageView release];
  [UIImagePNGRepresentation(newThumbnail) writeToFile:cachedImagePath atomically:YES];
  [self performSelectorOnMainThread:@selector(updateCellImage) withObject:nil waitUntilDone:NO];
}

这是主线程中用于刷新 tableview 的代码:

- (void)updateCellImage:(id)package
{
  needReloadCachedImages = YES;
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
  // I know, I know, there's a race condition here.. I'll fix it if this code stays.
  if(needReloadCachedImages)
    [self.tableView reloadData];

  needReloadCachedImages = NO;
}

有什么想法吗?

【问题讨论】:

    标签: ios uitableview concurrency lazy-loading nsoperation


    【解决方案1】:

    但这似乎很浪费:我每次都重新加载整个表 单元格的新图像已准备就绪。

    reloadData 只重新加载可见单元格,而不是整个表格,这就是你说的你想要的。

    【讨论】:

    • 哦,我不知道。我想问题在于它仍然会减慢向下滚动的速度。我想知道是否有办法让表格在更新视图时保持平滑滚动。
    【解决方案2】:

    给open-source一个try怎么样?对于一个更简单的问题来说,这太过分了。这里还有a nice tutorial,它可能会让你知道你可能做错了什么。

    【讨论】:

    • 我认为这并不费力。 'nice tutorial' 执行相同的线程,只是它隐藏在 NSURLConnection 中(它也跨越一个线程)。我从教程中意识到的关键区别是我将不得不子类化:所以回调发生在视图内部,并且视图可以重新加载自身。这样我就不会弄乱 reloadData。
    • 还有苹果的例子,LazyTableImages,真的很值得一看。
    猜你喜欢
    • 1970-01-01
    • 2016-06-27
    • 2011-10-01
    • 1970-01-01
    • 2013-08-15
    • 2014-06-14
    • 2013-05-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多