如何让UITableViewCell图像更新为下载的图像,而无需滚动UITableView

时间:2011-10-24 10:50:59

标签: ios uitableview concurrency lazy-loading nsoperation

我正在尝试通常使用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. 

然而,我无法弄清楚如何让细胞更新它们的图像,除非我手动将它们滚动并返回屏幕。我能够完成的唯一一个hack就是让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;
}

有什么想法吗?

2 个答案:

答案 0 :(得分:2)

  

但这似乎很浪费:我每次都要重新加载整张桌子   细胞的新图像准备就绪。

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

答案 1 :(得分:1)

如何给open-source try a nice tutorial一些?这对于更简单的问题来说太费力了。此处还有{{3}},可能会让您了解自己可能做错了什么。