我写了两种方法来在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 :(得分:217)
假设您正在寻找快速的战术修复,您需要做的是确保单元格图像已初始化,并且单元格的行仍然可见,例如:
- (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
任意图像视图的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 :(得分:14)
/ *我这样做了,并且还测试了它* /
步骤1 =在viewDidLoad方法中为表格注册自定义单元格类(如果是表格中的原型单元格)或nib(对于自定义单元格的自定义笔尖):
[self.yourTableView registerClass:[CustomTableViewCell class] forCellReuseIdentifier:@"CustomCell"];
OR
[self.yourTableView registerNib:[UINib nibWithNibName:@"CustomTableViewCell" bundle:nil] forCellReuseIdentifier:@"CustomCell"];
步骤2 =使用UITableView的“dequeueReusableCellWithIdentifier:forIndexPath:”方法(为此,您必须注册class或nib):
- (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;
}
答案 2 :(得分:13)
有多个框架可以解决这个问题。仅举几例:
夫特:
目标-C:
答案 3 :(得分:5)
我使用NSCache为图像加载器编写自己的light实现。 无细胞图像闪烁!
typealias ImageCacheLoaderCompletionHandler = ((UIImage) -> ())
class ImageCacheLoader {
var task: URLSessionDownloadTask!
var session: URLSession!
var cache: NSCache<NSString, UIImage>!
init() {
session = URLSession.shared
task = URLSessionDownloadTask()
self.cache = NSCache()
}
func obtainImageWithPath(imagePath: String, completionHandler: @escaping ImageCacheLoaderCompletionHandler) {
if let image = self.cache.object(forKey: imagePath as NSString) {
DispatchQueue.main.async {
completionHandler(image)
}
} else {
/* You need placeholder image in your assets,
if you want to display a placeholder to user */
let placeholder = #imageLiteral(resourceName: "placeholder")
DispatchQueue.main.async {
completionHandler(placeholder)
}
let url: URL! = URL(string: imagePath)
task = session.downloadTask(with: url, completionHandler: { (location, response, error) in
if let data = try? Data(contentsOf: url) {
let img: UIImage! = UIImage(data: data)
self.cache.setObject(img, forKey: imagePath as NSString)
DispatchQueue.main.async {
completionHandler(img)
}
}
})
task.resume()
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Identifier")
cell.title = "Cool title"
imageLoader.obtainImageWithPath(imagePath: viewModel.image) { (image) in
// Before assigning the image, check whether the current cell is visible
if let updateCell = tableView.cellForRow(at: indexPath) {
updateCell.imageView.image = image
}
}
return cell
}
答案 4 :(得分:3)
最好的答案不是正确的方法:(。你实际上将indexPath与模型绑定在一起,这并不总是好的。想象一下,在加载图像时添加了一些行。现在屏幕上存在给定indexPath的单元格,但是形象不再正确!情况不太可能,很难复制但是有可能。
最好使用MVVM方法,在控制器中使用viewModel绑定单元格并在viewModel中加载图像(使用switchToLatest方法指定ReactiveCocoa信号),然后订阅此信号并将图像分配给单元格! ;)
你必须记住不要滥用MVVM。观点必须简单明了!而ViewModels应该是可重用的!这就是将View(UITableViewCell)和ViewModel绑定在控制器中非常重要的原因。
答案 5 :(得分:3)
在我的情况下,它不是由于图像缓存(使用SDWebImage)。这是因为自定义单元格的标签与indexPath.row不匹配。
on cellForRowAtIndexPath:
1)为自定义单元格指定索引值。例如,
cell.tag = indexPath.row
2)在主线程上,在分配图像之前,通过将图像与标记匹配来检查图像是否属于相应的单元格。
dispatch_async(dispatch_get_main_queue(), ^{
if(cell.tag == indexPath.row) {
UIImage *tmpImage = [[UIImage alloc] initWithData:imgData];
thumbnailImageView.image = tmpImage;
}});
});
答案 6 :(得分:3)
这是swift版本(使用@Nitesh Borad目标C代码): -
if let img: UIImage = UIImage(data: previewImg[indexPath.row]) {
cell.cardPreview.image = img
} else {
// The image isn't cached, download the img data
// We should perform this in a background thread
let imgURL = NSURL(string: "webLink URL")
let request: NSURLRequest = NSURLRequest(URL: imgURL!)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
let error = error
let data = data
if error == nil {
// Convert the downloaded data in to a UIImage object
let image = UIImage(data: data!)
// Store the image in to our cache
self.previewImg[indexPath.row] = data!
// Update the cell
dispatch_async(dispatch_get_main_queue(), {
if let cell: YourTableViewCell = tableView.cellForRowAtIndexPath(indexPath) as? YourTableViewCell {
cell.cardPreview.image = image
}
})
} else {
cell.cardPreview.image = UIImage(named: "defaultImage")
}
})
task.resume()
}
答案 7 :(得分:2)
- (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;
}
答案 8 :(得分:1)
谢谢你&#34; Rob&#34; ....我和UICollectionView有同样的问题,你的回答帮助我解决了我的问题。 这是我的代码:
if ([Dict valueForKey:@"ImageURL"] != [NSNull null])
{
cell.coverImageView.image = nil;
cell.coverImageView.imageURL=nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([Dict valueForKey:@"ImageURL"] != [NSNull null] )
{
dispatch_async(dispatch_get_main_queue(), ^{
myCell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];
if (updateCell)
{
cell.coverImageView.image = nil;
cell.coverImageView.imageURL=nil;
cell.coverImageView.imageURL=[NSURL URLWithString:[Dict valueForKey:@"ImageURL"]];
}
else
{
cell.coverImageView.image = nil;
cell.coverImageView.imageURL=nil;
}
});
}
});
}
else
{
cell.coverImageView.image=[UIImage imageNamed:@"default_cover.png"];
}
答案 9 :(得分:0)
我认为您希望在背景中为单元格加载图像时加快单元格加载速度。为此,我们完成了以下步骤:
检查文件是否存在于文件目录中。
如果没有,则首次加载图像并保存到 我们的手机文档目录。如果您不想将图像保存在手机中,则可以直接在后台加载单元格图像。
现在加载过程:
只需加入:#import "ManabImageOperations.h"
单元格的代码如下所示:
NSString *imagestr=[NSString stringWithFormat:@"http://www.yourlink.com/%@",[dictn objectForKey:@"member_image"]];
NSString *docDir=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
NSLog(@"Doc Dir: %@",docDir);
NSString *pngFilePath = [NSString stringWithFormat:@"%@/%@",docDir,[dictn objectForKey:@"member_image"]];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:pngFilePath];
if (fileExists)
{
[cell1.memberimage setImage:[UIImage imageWithContentsOfFile:pngFilePath] forState:UIControlStateNormal];
}
else
{
[ManabImageOperations processImageDataWithURLString:imagestr andBlock:^(NSData *imageData)
{
[cell1.memberimage setImage:[[UIImage alloc]initWithData: imageData] forState:UIControlStateNormal];
[imageData writeToFile:pngFilePath atomically:YES];
}];
}
<强> ManabImageOperations.h:强>
#import <Foundation/Foundation.h>
@interface ManabImageOperations : NSObject
{
}
+ (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage;
@end
<强> ManabImageOperations.m:强>
#import "ManabImageOperations.h"
#import <QuartzCore/QuartzCore.h>
@implementation ManabImageOperations
+ (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage
{
NSURL *url = [NSURL URLWithString:urlString];
dispatch_queue_t callerQueue = dispatch_get_main_queue();
dispatch_queue_t downloadQueue = dispatch_queue_create("com.myapp.processsmagequeue", NULL);
dispatch_async(downloadQueue, ^{
NSData * imageData = [NSData dataWithContentsOfURL:url];
dispatch_async(callerQueue, ^{
processImage(imageData);
});
});
// downloadQueue=nil;
dispatch_release(downloadQueue);
}
@end
如果出现任何问题,请检查答案并发表评论....
答案 10 :(得分:0)
只需更改,
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];
});
});
<强>向强>
dispatch_async(kBgQueue, ^{
NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString: [NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];
cell.poster.image = [UIImage imageWithData:imgData];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
});
});
答案 11 :(得分:0)
您只需传递您的网址
即可NSURL *url = [NSURL URLWithString:@"http://www.myurl.com/1.png"];
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(), ^{
yourimageview.image = image;
});
}
}
}];
[task resume];
答案 12 :(得分:-1)
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
Static NSString *CellIdentifier = @"Cell";
QTStaffViewCell *cell = (QTStaffViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
If (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"QTStaffViewCell" owner:self options:nil];
cell = [nib objectAtIndex: 0];
}
StaffData = [self.staffArray objectAtIndex:indexPath.row];
NSString *title = StaffData.title;
NSString *fName = StaffData.firstname;
NSString *lName = StaffData.lastname;
UIFont *FedSanDemi = [UIFont fontWithName:@"Aller" size:18];
cell.drName.text = [NSString stringWithFormat:@"%@ %@ %@", title,fName,lName];
[cell.drName setFont:FedSanDemi];
UIFont *aller = [UIFont fontWithName:@"Aller" size:14];
cell.drJob.text = StaffData.job;
[cell.drJob setFont:aller];
if ([StaffData.title isEqualToString:@"Dr"])
{
cell.drJob.frame = CGRectMake(83, 26, 227, 40);
}
else
{
cell.drJob.frame = CGRectMake(90, 26, 227, 40);
}
if ([StaffData.staffPhoto isKindOfClass:[NSString class]])
{
NSURL *url = [NSURL URLWithString:StaffData.staffPhoto];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:url
completionHandler:^(NSURL *location,NSURLResponse *response, NSError *error) {
NSData *imageData = [NSData dataWithContentsOfURL:location];
UIImage *image = [UIImage imageWithData:imageData];
dispatch_sync(dispatch_get_main_queue(),
^{
cell.imageView.image = image;
});
}];
[task resume];
}
return cell;}