在 cellForRowAtIndexPath 中使用 dispatch_async 加载图像

Loading a image using dispatch_async within cellForRowAtIndexPath

我正在尝试从 url 加载图像。

以下是我的代码..

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

  UITableViewCell *cell = nil;

    cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:@""];

    if (cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                      reuseIdentifier:nil];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }

    dispatch_async (dispatch_get_main_queue(), ^{

        NSData * storeImageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:productImageArray[indexPath.row]]];

        self.productImage.image = [UIImage imageWithData:storeImageData];
        });

            [cell.contentView addSubview:self.productImage];

return cell;
}

问题是,

  1. UI 冻结,直到图像加载完毕。
  2. 只有最后一个单元格正在加载图像,其余单元格未加载图像。

我该如何解决这个问题?

尝试 NSURLSessionTask :

NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (data) {
        UIImage *image = [UIImage imageWithData:data];
        if (image) {
            dispatch_async(dispatch_get_main_queue(), ^{
                UITableViewCell * cell = (id)[tableView cellForRowAtIndexPath:indexPath];
                if (cell)
                    cell. productImage.image = image;
            });
        }
    }
}];
[task resume];

您正在主 UI 线程上同步 下载图像,这导致屏幕冻结。

请按照以下文章解决问题:

iOS: How To Download Images Asynchronously (And Make Your UITableView Scroll Fast)

也回答你的第二点:

您似乎使用了在 cellForRowAtIndexPath 方法之外实例化的单个 imageView,然后您试图将其添加到每个单元格中,这将导致图像从先前的单元格中删除,并添加到当前单元格中

因为一个视图只能有一个父视图,一旦您尝试在其他视图中添加子视图,它就会从当前的父视图中删除

您可以使用 GCD 在后台线程中加载图像,如下所示:

  //get a dispatch queue

    dispatch_queue_t concurrentQueue =  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    //this will start the image loading in bg

    dispatch_async(concurrentQueue, ^{        
        NSData *image = [[NSData alloc] initWithContentsOfURL:imageURL];

        //this will set the image when loading is finished

        dispatch_async(dispatch_get_main_queue(), ^{
            imageView.image = [UIImage imageWithData:image];
        });
    });

但是解决这些问题的最简单方法是使用 UIImageView 类别,例如 SDWebImage or AFNetworking 提供的类别。如果你愿意,你可以自己写代码来处理上面的问题,但是工作量很大,上面的UIImageView类已经帮你搞定了。

试试这个:

    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    cell.imageView.image = image;  

或者有时我所做的是更新模型,然后在特定的 indexPath 处重新加载单元格:

  myModel.image = downloadedThumbImage;
  [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                                  withRowAnimation:UITableViewRowAnimationNone];  


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

   //getting the selected row image 
  NSDictionary* currentImageDictionary=[self.topfifty objectAtIndex:indexPath.row];//topFifty is an array of image dictionaries

   UIImage *currentImage = [currentImageDictionary objectForKey:@"image"];

  if (currentImage) { 
      // we already fetched the image, so we just set it to the cell's image view
     cell.imageView.image = currentImage;
  }
  else {
       // we don't have the image, so we need to fetch it from the server  

   // In the meantime, we can set some place holder image
   UIImage *palceholderImage = [UIImage imageNamed:@"placeholder.png"];
   cell.imageView.image = palceholderImage;

  // set the placeholder as the current image to your model, so you won't 
  // download the image multiple times (can happen if you reload this cell while 
  // download is in progress)
  [currentImageDictionary setObject:palceholderImage forKey:@"image"];

  // then download the image
  // creating the download queue 
  dispatch_queue_t downloadQueue=dispatch_queue_create("thumbnailImage", NULL);

  dispatch_async(downloadQueue, ^{
     UIImage *downloadedThumbImage=[self getImage:currentImageDictionary] ;

     //Need to go back to the main thread since this is UI related
     dispatch_async(dispatch_get_main_queue(), ^{
            // store the downloaded image in your model
            [currentImageDictionary setObject:image forKey:@"image"];

            // update UI
            UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
            cell.imageView.image = image;    
     });
  });
  dispatch_release(downloadQueue);

   }

 return cell;
 }

我觉得这是异步下载图像的最佳方法。我将始终使用以下方法完成此任务。

cellForRowAtIndexPath:

 NSURL *imgURL = [[NSURL URLWithString:productImageArray[indexPath.row]];
 [self downloadImageWithURL:imgURL
            completionBlock:^(BOOL succeeded, UIImage *image) {
                          self.productImage.image=image;
     }];

并添加此方法

- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
     [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
          completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
             if ( !error )
              {
                data = [NSData dataWithContentsOfURL:url];
                UIImage *image = [[UIImage alloc] initWithData:data];
                completionBlock(YES,image);
              } else{
                 NSLog(@"Error in downloading image:%@",url);
                 completionBlock(NO,nil);
              }
           }];
}

使用这个,即使我们在下载图片时有任何问题也能更好地知道。

@MGR,你可以使用 Dispatch async,但是有一个库 https://www.cocoacontrols.com/controls/hanekeswift,一个很棒的库。它将处理下载图像,并且有一种方法可以在 imageview.public 中加载图像 func hnk_setImageFromURL(URL: NSURL, placeholder: UIImage? = default, format: Haneke.Format<UIImage>? = default, failure fail: ((NSError?) -> ())? = default, success succeed: ((UIImage) -> ())? = default)

它还有缓存功能。所以你可以简单地在 cellForRow 中实现这个功能。那 it.evertything 将由 Haneke 处理。

* 请使用此框架 * https://github.com/onevcat/Kingfisher

这带来了比 Hanekeswift

更好的性能和自定义