我写了两种方法来在我的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;
}
13 回答
假设您正在寻找快速的战术修复,您需要做的是确保单元格图像已初始化,并且单元格的行仍然可见,例如:
上面的代码解决了一个问题,这个问题源于该单元被重用的事实:
在启动后台请求之前,您没有初始化单元格图像(这意味着在下载新图像时,出列单元格的最后一个图像仍然可见) . 确保
nil
任何图像视图的image
属性,否则您将看到图像的闪烁 .一个更微妙的问题是,在一个非常慢的网络上,您的异步请求可能无法在单元格滚出屏幕之前完成 . 您可以使用
UITableView
方法cellForRowAtIndexPath:
(不要与类似名称的UITableViewDataSource
方法tableView:cellForRowAtIndexPath:
混淆)以查看该行的单元格是否仍然可见 . 如果单元格不可见,此方法将返回nil
.问题是,当您的异步方法完成时,单元格已经滚动,更糟糕的是,单元格已被重用于表格的另一行 . 通过检查该行是否仍然可见,您将确保不会意外地使用自从屏幕滚动的行的图像更新图像 .
与手头的问题有点无关,我仍然觉得有必要更新它以利用现代约定和API,特别是:
使用
NSURLSession
而不是将-[NSData contentsOfURL:]
分派给后台队列;使用
dequeueReusableCellWithIdentifier:forIndexPath:
而不是dequeueReusableCellWithIdentifier:
(但请确保使用单元格原型或注册类或NIB作为该标识符);和我使用了符合Cocoa naming conventions的类名(即以大写字母开头) .
即使进行了这些更正,也存在以下问题:
上面的代码没有缓存下载的图像 . 这意味着如果您在屏幕上滚动图像并返回屏幕,应用程序可能会尝试再次检索图像 . 也许你很幸运,你的服务器响应头将允许
NSURLSession
和NSURLCache
提供相当透明的缓存,但如果没有,你将会做出不必要的服务器请求并提供更慢的UX .我们没有取消滚动屏幕的单元格请求 . 因此,如果您快速滚动到第100行,该行的图像可能会滞后于之前甚至不可见的前99行的请求 . 您总是希望确保优先考虑可见单元格的请求,以获得最佳用户体验 .
解决这些问题的最简单方法是使用
UIImageView
类别,例如SDWebImage或AFNetworking提供的类别 . 如果你愿意,你可以编写自己的代码来处理上述问题,但这是很多工作,上面的UIImageView
类别已经为你做了这个 ./ 我这样做了,还测试了它 /
步骤1 =在viewDidLoad方法中为这样的表注册自定义单元类(如果是表格中的原型单元格)或nib(对于自定义单元格的自定义nib)
要么
步骤2 =使用UITableView的“dequeueReusableCellWithIdentifier:forIndexPath:”方法(为此,您必须注册class或nib):
有多个框架可以解决这个问题 . 仅举几个:
迅速:
Nuke(我的)
Kingfisher
AlamofireImage
HanekeSwift
Objective-C的:
AFNetworking
PINRemoteImage
YYWebImage
SDWebImage
斯威夫特3
我使用NSCache为图像加载器编写了自己的light实现 . No cell image flickering!
ImageCacheLoader.swift
用法示例
最好的答案不是正确的方法:( . 你实际上将indexPath与模型绑定,这并不总是好的 . 想象一下,在加载图像时已经添加了一些行 . 现在,给定indexPath的单元格存在于屏幕上,但是图像不再是正确的!情况有点不太可能并且难以复制,但这是可能的 .
最好使用MVVM方法,在控制器中使用viewModel绑定单元格并在viewModel中加载图像(使用switchToLatest方法分配ReactiveCocoa信号),然后订阅此信号并将图像分配给单元格! ;)
你必须记住不要滥用MVVM . 观点必须简单明了!而ViewModels应该是可重用的!这就是在控制器中绑定View(UITableViewCell)和ViewModel非常重要的原因 .
在我的情况下,它不是由于图像缓存(使用SDWebImage) . 这是因为自定义单元格的标记与indexPath.row不匹配 .
在cellForRowAtIndexPath上:
1)为自定义单元格指定索引值 . 例如,
2)在主线程上,在分配图像之前,通过将图像与标记匹配来检查图像是否属于相应的单元格 .
这是swift版本(使用@Nitesh Borad目标C代码): -
谢谢你“抢”....我和UICollectionView有同样的问题,你的答案帮助我解决了我的问题 . 这是我的代码:
我想你想加快你在背景中为单元格加载图像时的单元格加载速度 . 为此,我们完成了以下步骤:
检查文件是否存在于文档目录中 .
如果没有,则首次加载图像,并将其保存到手机文档目录 . 如果您不想将图像保存在手机中,则可以直接在后台加载单元格图像 .
现在加载过程:
只需包括:
#import "ManabImageOperations.h"
对于单元格,代码如下所示:
ManabImageOperations.h:
ManabImageOperations.m:
如果有任何问题,请检查答案和评论....
Simply change,
Into
您只需传递您的网址即可