Как заставить изображения UITableViewCell обновляться до загруженных изображений без прокрутки UITableView

Я пытаюсь использовать свой собственный вариант обычной техники 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];
}

И это код в основном потоке для обновления таблицы:

- (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;
}

Есть идеи?


person SaldaVonSchwartz    schedule 24.10.2011    source источник


Ответы (2)


Но это кажется расточительным: я перезагружаю всю таблицу каждый раз, когда новое изображение ячейки готово.

reloadData перезагружает только видимые ячейки, а не всю таблицу, как вы говорите, что хотите.

person Paul Lynch    schedule 25.10.2011
comment
о, я этого не знал. Я думаю, проблема в том, что он все еще замедляет прокрутку вниз. Интересно, есть ли способ, чтобы таблица продолжала плавно прокручиваться при обновлении представлений. - person SaldaVonSchwartz; 25.10.2011

Как насчет предоставления открытого кода попробовать? Это слишком много усилий для более простой задачи. Существует также хорошее руководство по этому вопросу, которое может дать вам представление о что вы можете делать неправильно.

person zakishaheen    schedule 25.10.2011
comment
Я не думаю, что это слишком много усилий. «Хороший учебник» выполняет те же потоки, только если они скрыты в NSURLConnection (который также охватывает поток). Ключевое отличие, которое я понял из учебника, заключается в том, что мне придется создать подкласс: поэтому обратный вызов происходит внутри представления, и представление может перезагружаться. Таким образом, я не связываюсь с reloadData. - person SaldaVonSchwartz; 25.10.2011
comment
Есть также пример Apple, LazyTableImages, на который действительно стоит обратить внимание. - person Alex Zavatone; 16.08.2013