我已经编写了两种方法来异步加载我的 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;
}
最佳答案
假设您正在寻找快速的战术修复,您需要做的是确保单元格图像已初始化并且单元格的行仍然可见,例如:
- (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;
}
上面的代码解决了由于单元被重用的事实而产生的一些问题:
在启动后台请求之前,您没有初始化单元格图像(这意味着在下载新图像时,出列单元格的最后一张图像仍然可见)。确保
nil
任何 ImageView 的image
属性,否则您会看到图像闪烁。一个更微妙的问题是,在一个非常慢的网络上,您的异步请求可能不会在单元格滚出屏幕之前完成。您可以使用
UITableView
方法cellForRowAtIndexPath:
(不要与名称相似的UITableViewDataSource
方法tableView:cellForRowAtIndexPath:
) 查看该行的单元格是否仍然可见。如果单元格不可见,此方法将返回nil
。问题在于,当您的异步方法完成时,单元格已滚动,更糟糕的是,该单元格已被重新用于表格的另一行。通过检查该行是否仍然可见,您可以确保不会意外地使用已滚动离开屏幕的行的图像来更新图像。
与手头的问题有点无关,我仍然觉得有必要更新它以利用现代约定和 API,特别是:
使用
NSURLSession
而不是将-[NSData contentsOfURL:]
分派(dispatch)到后台队列;使用
dequeueReusableCellWithIdentifier:forIndexPath:
而不是dequeueReusableCellWithIdentifier:
(但请确保使用单元原型(prototype)或注册类或 NIB 作为该标识符);和我使用了符合 Cocoa naming conventions 的类名(即以大写字母开头)。
即使进行了这些更正,也存在一些问题:
上面的代码没有缓存下载的图片。这意味着如果您在屏幕上滚动图像并返回屏幕,应用程序可能会尝试再次检索图像。也许您会很幸运,您的服务器响应 header 将允许
NSURLSession
和NSURLCache
提供的相当透明的缓存,但如果没有,您将发出不必要的服务器请求并提供更慢的用户体验。我们不会取消对滚动到屏幕外的单元格的请求。因此,如果您快速滚动到第 100 行,则该行的图像可能会积压在对前 99 行的请求之后,这些请求甚至不再可见。您总是希望确保优先考虑对可见单元格的请求以获得最佳用户体验。
解决这些问题的最简单方法是使用 UIImageView
类别,例如 SDWebImage 提供的类别。或 AFNetworking .如果您愿意,您可以编写自己的代码来处理上述问题,但工作量很大,而且上面的 UIImageView
类别已经为您完成了。
关于ios - 从 UITableView 单元格内的 url 异步加载图像 - 滚动时图像更改为错误图像,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16663618/