用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/UIActivityIndicator-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) } }); }