用GCDasynchronous下载UITableView的图像

我正在使用GCDasynchronous下载我的uitableview的图像,但是存在一个问题 – 滚动图像闪烁并且一直改变。 我试图设置图像与每个细胞零,但没有多大帮助。 当快速滚动时,所有图像都是错误的。 我能做些什么? 这是我的单元格的方法:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; if (self.loader.parsedData[indexPath.row] != nil) { cell.imageView.image = nil; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); dispatch_async(queue, ^(void) { NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self.loader.parsedData[indexPath.row] objectForKey:@"imageLR"]]]; UIImage* image = [[UIImage alloc] initWithData:imageData]; dispatch_async(dispatch_get_main_queue(), ^{ cell.imageView.image = image; [cell setNeedsLayout]; }); }); cell.textLabel.text = [self.loader.parsedData[indexPath.row] objectForKey:@"id"]; } return cell; } 

这里的问题是你的图像获取块保存对tableview单元格的引用。 下载完成后,即使您已经回收单元格以显示不同的行,也会设置imageView.image属性。

在设置图像之前,您需要使用下载完成块来testing图像是否与单元格相关。

另外值得注意的是,你不是将图像存储在单元格以外的任何位置,所以每次在屏幕上滚动一行时都会再次下载它们。 在开始下载之前,您可能需要将它们caching在某处并查找本地caching的图像。

编辑:这是一个简单的方法来testing,使用单元格的tag属性:

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; cell.tag = indexPath.row; NSDictionary *parsedData = self.loader.parsedData[indexPath.row]; if (parsedData) { cell.imageView.image = nil; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); dispatch_async(queue, ^(void) { NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:parsedData[@"imageLR"]]; UIImage* image = [[UIImage alloc] initWithData:imageData]; if (image) { dispatch_async(dispatch_get_main_queue(), ^{ if (cell.tag == indexPath.row) { cell.imageView.image = image; [cell setNeedsLayout]; } }); } }); cell.textLabel.text = parsedData[@"id"]; } return cell; } 

关键是你没有完全理解单元重用的概念。 这与asynchronous下载不太一致。

该块

  ^{ cell.imageView.image = image; [cell setNeedsLayout]; } 

当请求完成并且所有数据被加载时被执行。 但是单元格在块创build时获得它的值。

到块执行的时候,单元仍然指向现有单元之一。 但用户继续滚动的可能性很大。 细胞对象在此期间被重新使用,并且图像与被重新使用并分配和显示的“ ”细胞相关联。 不久之后,正确的图像被加载并分配并显示,除非用户已经进一步滚动。 等等等等。

你应该寻找一个更聪明的方式来做到这一点。 有很多turotials。 谷歌为懒惰图像加载。

使用索引path来获取单元格。 如果它不可见,那么这个单元将是nil ,你将不会有问题。 当然,你可能想要在下载时caching数据,以便在已经有图像时立即设置单元格的图像。

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; if (self.loader.parsedData[indexPath.row] != nil) { cell.imageView.image = nil; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); dispatch_async(queue, ^(void) { // You may want to cache this explicitly instead of reloading every time. NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self.loader.parsedData[indexPath.row] objectForKey:@"imageLR"]]]; UIImage* image = [[UIImage alloc] initWithData:imageData]; dispatch_async(dispatch_get_main_queue(), ^{ // Capture the indexPath variable, not the cell variable, and use that UITableViewCell *blockCell = [tableView cellForRowAtIndexPath:indexPath]; blockCell.imageView.image = image; [blockCell setNeedsLayout]; }); }); cell.textLabel.text = [self.loader.parsedData[indexPath.row] objectForKey:@"id"]; } return cell; } 

我一直在研究这个问题,通过定制一个UITableViewCell,我发现了一个很好的方法。

 #import <UIKit/UIKit.h> @interface MyCustomCell : UITableViewCell @property (nonatomic, strong) NSURLSessionDataTask *imageDownloadTask; @property (nonatomic, weak) IBOutlet UIImageView *myImageView; @property (nonatomic, weak) IBOutlet UIActivityIndicatorView *activityIndicator; @end 

现在,在你的TableViewController中,为NSURLSessionConfiguration和NSURLSession声明两个属性,并在ViewDidLoad中初始化它们:

 @interface MyTableViewController () @property (nonatomic, strong) NSURLSessionConfiguration *sessionConfig; @property (nonatomic, strong) NSURLSession *session; . . . @end @implementation TimesVC - (void)viewDidLoad { [super viewDidLoad]; _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; _session = [NSURLSession sessionWithConfiguration:_sessionConfig]; } . . . 

假设你的数据源是一个NSMutableDictionary的数组(或NSManagedObjectContext)。 您可以轻松地下载caching的每个单元格的图像,如下所示:

 . . . - (MyCustomCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { MyCustomCell *cell = (MyCustomCell *)[tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; if (!cell) { cell = [[MyCustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; } NSMutableDictionary *myDictionary = [_myArrayDataSource objectAtIndex:indexPath.row]; if (cell.imageDownloadTask) { [cell.imageDownloadTask cancel]; } [cell.activityIndicator startAnimating]; cell.myImageView.image = nil; if (![myDictionary valueForKey:@"image"]) { NSString *urlString = [myDictionary valueForKey:@"imageURL"]; NSURL *imageURL = [NSURL URLWithString:urlString]; if (imageURL) { cell.imageDownloadTask = [_session dataTaskWithURL:imageURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (error) { NSLog(@"ERROR: %@", error); } else { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; if (httpResponse.statusCode == 200) { UIImage *image = [UIImage imageWithData:data]; dispatch_async(dispatch_get_main_queue(), ^{ [myDictionary setValue:data forKey:@"image"]; [cell.myImageView setImage:image]; [cell.activityIndicator stopAnimating]; }); } else { NSLog(@"Couldn't load image at URL: %@", imageURL); NSLog(@"HTTP %d", httpResponse.statusCode); } } }]; [cell.imageDownloadTask resume]; } } else { [cell.myImageView setImage:[UIImage imageWithData:[myDictionary valueForKey:@"image"]]]; [cell.activityIndicator stopAnimating]; } return cell; } 

我希望它能帮助一些开发者! 干杯。

CREDITS: iOS 7中的表格视图

不要重新发明轮子,只要使用下面这些令人敬畏的豆荚:

https://github.com/rs/SDWebImage

https://github.com/JJSaccolo/UIActivityIndi​​cator-for-SDWebImage

简单如下:

  [self.eventImage setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", [SchemeConfiguration APIEndpoint] , _event.imageUrl]] placeholderImage:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { event.image = UIImagePNGRepresentation(image); } usingActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; 

你有没有想过使用SDWebImage? 它也asynchronous地从给定的URL下载图像。 我没有使用整个库(只导入UIImageView + WebCache.h)。 一旦导入,你所要做的就是调用这个方法:

 [UIImageXYZ sd_setImageWithURL:["url you're retrieving from"] placeholderImage:[UIImage imageNamed:@"defaultProfile.jpeg"]]; 

如果你使用的是AFNetworking 2.0,可能会有点过分,但是对我来说却是成功的。

这里是链接到github,如果你想尝试一下

除了Seamus Campbell接受的答案外,你也应该知道有时候这是行不通的。 在这种情况下,我们应该重新加载特定的单元格。 所以

 if (image) { dispatch_async(dispatch_get_main_queue(), ^{ if (cell.tag == indexPath.row) { cell.imageView.image = image; [cell setNeedsLayout]; } }); } 

应该改成,

  if (image) { dispatch_async(dispatch_get_main_queue(), ^{ if (cell.tag == indexPath.row) { cell.imageView.image = image; self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None) } }); }