ios - scrolling单元格图像中的异步图像加载在滚动时改变为错误图像

  显示原文与译文双语对照的内容
121 0

我已经编写了两种方法在UITableView单元格中异步加载图片。在这两种情况下,图像将加载好,但当我滚动表时图像将会变化几次,图像将返回正确的图像。我不清楚为什么会发生这种情况。

#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
- (void)viewDidLoad
{
 [super viewDidLoad];
 dispatch_async(kBgQueue, ^{
 NSData* data = [NSData dataWithContentsOfURL: [NSURL URLWithString:
 @"http://myurl.com/getMovies.php"]];
 [self performSelectorOnMainThread:@selector(fetchedData:)
 withObject:data waitUntilDone:YES];
 });
}
-(void)fetchedData:(NSData *)data
{
 NSError* error;
 myJson = [NSJSONSerialization
 JSONObjectWithData:data
 options:kNilOptions
 error:&error];
 [_myTableView reloadData];
} 
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
//Return the number of sections.
 return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
//Return the number of rows in the section.
//Usually the number of items in your array (the one that holds your list)
 NSLog(@"myJson count: %d",[myJson count]);
 return [myJson count];
}
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
 myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
 if (cell == nil) {
 cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
 }
 dispatch_async(kBgQueue, ^{
 NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];
 dispatch_async(dispatch_get_main_queue(), ^{
 cell.poster.image = [UIImage imageWithData:imgData];
 });
 });
 return cell;
}

。。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
 myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
 if (cell == nil) {
 cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
 }
 NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]];
 NSURLRequest* request = [NSURLRequest requestWithURL:url];
 [NSURLConnection sendAsynchronousRequest:request
 queue:[NSOperationQueue mainQueue]
 completionHandler:^(NSURLResponse * response,
 NSData * data,
 NSError * error) {
 if (!error){
 cell.poster.image = [UIImage imageWithData:data];
//do whatever you want with image
 }
 }];
 return cell;
}
时间:原作者:0个回答

150 1

假设你正在寻找快速战术修复,需要确保单元格图像已经初始化并且单元格行仍可以见 e.g:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
 cell.poster.image = nil;//or cell.poster.image = [UIImage imageNamed:@"placeholder.png"];
 NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg", self.myJson[indexPath.row][@"movieId"]]];
 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(), ^{
 MyCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
 if (updateCell)
 updateCell.poster.image = image;
 });
 }
 }
 }];
 [task resume];
 return cell;
}

上述代码解决了由于该单元重用而导致的一些问题:

你在启动背景请求( 意味着dequeued单元格的最后一个图像在下载新图像时仍然可见) 之前不初始化单元格图像。确保 nil 属性的image 属性,否则你将看到图像闪烁。

更微妙的问题是,在非常慢的网络上,异步请求在单元格滚动屏幕之前可能无法完成。如果该行的单元格仍然可见,则可以使用 UITableView 方法 cellForRowAtIndexPath: ( 不要与类似的命名 UITableViewDataSource 方法 tableView:cellForRowAtIndexPath: 混淆) 来查看该行。如果单元格不可见,这里方法将返回 nil

问题是当异步方法完成时单元格已经经滚动,更糟糕的是单元格已经经重用了另一行。通过检查该行是否仍然可以见,你将确保不会随后将图像与从屏幕上滚动的图像一起更新。

我仍然觉得必须更新这个问题,以利用现代的约定和 API,尤它的是:

  • 使用 NSURLSession 而不是将 -[NSData contentsOfURL:] 调度到后台队列;

  • 用户dequeueReusableCellWithIdentifier:forIndexPath:而不是 dequeueReusableCellWithIdentifier: ( 但是要确保使用单元格Prototype或者 register 类或者NIB作为标识符) ;以及

  • 我使用了一个符合 Cocoa 命名约定的类 NAME ( 例如 。从大写字母开始) 。

即使有了这些更正,也有问题:

上面的代码不缓存已经下载的图像。这意味着如果你在屏幕上滚动图像并回到屏幕上,应用程序可以能会再次尝试检索图像。你可以能会幸运的是,你的服务器响应头将允许 NSURLSessionNSURLCache 提供相当透明的缓存,但是如果不需要的话。

我们不会取消对滚动屏幕的单元格的请求。因这里,如果快速滚动到 100th 行,那么该行的图像可以能是对于前面 99行的请求。你总是希望确保对可以见单元格的请求优先级优先于最佳用户界面。

解决这些问题的简单修复是使用 UIImageView 类别,比如使用 SDWebImage 或者 AFNetworking 。如果你需要,你可以以编写你自己的代码来处理上面的问题,但是上面的UIImageView 类别已经经完成了。

原作者:
59 4

/* 我已经经这样做了,并且测试了 */

步骤 1 = register 自定义单元格类( 。表格中的Prototype单元格) 或者 nib ( 自定义单元格的自定义笔尖),类似于viewDidLoad方法中的表:

[self.yourTableView registerClass:[CustomTableViewCell class] forCellReuseIdentifier:@"CustomCell"];

或者

[self.yourTableView registerNib:[UINib nibWithNibName:@"CustomTableViewCell" bundle:nil] forCellReuseIdentifier:@"CustomCell"];

第 2步= 使用uitableview的dequeueReusableCellWithIdentifierforIndexPath:"这样的方法( 对于这个,你必须使用 register 类或者):

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 CustomTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCell" forIndexPath:indexPath];
 cell.imageViewCustom.image = nil;//[UIImage imageNamed:@"default.png"];
 cell.textLabelCustom.text = @"Hello";
 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//retrive image on global queue
 UIImage * img = [UIImage imageWithData:[NSData dataWithContentsOfURL: [NSURL URLWithString:kImgLink]]];
 dispatch_async(dispatch_get_main_queue(), ^{
 CustomTableViewCell * cell = (CustomTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
//assign cell image on main thread
 cell.imageViewCustom.image = img;
 });
 });
 return cell;
 }
原作者:
...