dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){

NSURL *url = [NSURL URLWithString: detailedActivity.pictures];

NSData *data = [[NSData alloc] initWithContentsOfURL:url];

dispatch_async(dispatch_get_main_queue(), ^(void){

self.activityImageView.image = [[UIImage alloc]initWithData:data];

});

});

本文转载至 http://stackoverflow.com/questions/16663618/async-image-loading-from-url-inside-a-uitableview-cell-image-changes-to-wrong

I've written two ways to async load pictures inside my UITableView cell. In both cases the image will load fine but when I'll scroll the table the images will change a few times until the scroll will end and the image will go back to the right image. I have no idea why this is happening.

#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;
}
asked May 21 '13 at 6:36
Segev
8,99262982
 
3  
You're trying to store information in the actual cells. This is bad, very bad. You should store information in n array (or something similar) and then display it in the cells. The information in this case is the actual UIImage. Yes load it asynchronously but load it into an array. –  Fogmeister May 21 '13 at 7:00
1  
@Fogmeister Are you referring to poster? That's presumably an imageview in his custom cell, so what EXEC_BAD_ACCESS is doing is perfectly right. You are correct that you should not use the cell as the repository for model data, but I don't think that's what he's doing. He's just giving the custom cell what it needs to present itself. Furthermore, and this is a more subtle issue, I would be wary about storing an image, itself, in your model array backing your tableview. It's better to use a image caching mechanism and your model object should retrieve from that cache. –  Rob May 21 '13 at 14:23
1  
Yes, exactly my point. Looking at the request (which is shown in full) he is downloading the image asynchronously and putting it directly into the imageView in the cell. (Thus using the cell to store the data, i.e. the image). What he should be doing is referencing an object and requesting the image from that object (contained in an array or somewhere). If the object doesn't yet have the image it should return a placeholder and download the image. Then when the image is downloaded and ready to display let the table know so it can update the cell (if it's visible). –  Fogmeister May 21 '13 at 14:28
1  
What he is doing will force the download every single time he scrolls to that cell in the table. Whether the images are stored persistently is up to him, but at least store them for the life time of the tableview. – Fogmeister May 21 '13 at 14:28 
1  
Exactly :D That way you only need to fetch the image from the URL once. You will see this on things like Facebook Friend Picker. When you start it all the avatars are grey placeholders. Then as you scroll they all fill in as it moves along. But then when you scroll back to a cell previously shown it will instantly show the already downloaded image. –  Fogmeister May 21 '13 at 14:41

7 Answers

 
 
 
 
 
 
 

Assuming you're looking for a quick tactical fix, what you need to do is make sure the cell image is initialized and also that the cell's row is still visible, e.g.:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

    myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (cell == nil) {
cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
} cell.poster.image = nil; // or cell.poster.image = [UIImage imageNamed:@"placeholder.png"]; dispatch_async(kBgQueue, ^{
NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];
if (imgData) {
UIImage *image = [UIImage imageWithData:imgData];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
myCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
if (updateCell)
updateCell.poster.image = image;
});
}
}
});
return cell;
}

The above code addresses two problems stemming from the fact that the cell is reused:

  1. You're not initializing the cell image before you initiating the background request (meaning that the last image for the dequeued cell will still be there). Reset that. That's the more significant problem here.

  2. A more subtle issue is that on a really slow network, your asynchronous request might not finish before the cell scrolls off the screen. You can use the UITableView method cellForRowAtIndexPath: (not to be confused with the similarly named UITableViewDataSource method tableView:cellForRowAtIndexPath:) to see if the cell for that row is still visible. This method will return nil if the cell is not visible.

    The issue is that the cell has scrolled off by the time your async method has completed, and, worse, the cell has been reused for another row of the table. By checking to see if the row is still visible, you'll ensure that you don't accidentally update the image with the image for a row that has since scrolled off the screen.

    This approach will also work with the sendAsynchronousRequest rendition, too.

An even better fix, though, is to use a UIImageView category, such as is provided with SDWebImage or AFNetworking, which will, handle this more efficiently canceling the request in progress if the cell's image view has been reused for another cell. On a very slow network, if you scroll quickly through the first, for example, 50 images for the first 50 rows of the tableview, your implementation (which is one I used to advocate until I better appreciated this subtlety) will continue trying to load images that have since scrolled off the screen, making it very slow to respond to that 51st image that you need for the currently visible cell.

These UIImageView categories also solve an additional problem with your code: cacheing. You really don't want to re-retrieve the image from the network when you scroll back and look at previous rows. Judicious use of NSCache (which those categories use) will dramatically improve performance and reduce redundant network requests.

If you want, you can write your own imageview category, but it's a lot of work, and SDWebImage or AFNetworking has done this already for you.

answered May 21 '13 at 6:47
Rob
120k12183285
 
    
Thanks. I believe you need to edit your answer. updateCell.poster.image = nil tocell.poster.image = nil; updateCell is called before it's declared. –  Segev May 21 '13 at 7:09
    
@EXEC_BAD_ACCESS quite right. Thanks. –  Rob May 21 '13 at 7:11
    
@EXEC_BAD_ACCESS AFNetworking is a general networking class that eliminates a lot of uglyNSURLConnection programming. SDWebImage is a smaller framework, focusing primarily on images being retrieved from the web. Both do a pretty good job on their UIImageView categories. –  Rob May 21 '13 at 7:30 
3  
@Rob At this moment, I love you more than I love my girlfriend, I don't have one but I'm sure I would love you more than her if thats the case, I had this stupid issue for a month now, and i'm delaying it everytime, but thanks to you NO MORE DELAYING! I fixed it! Make sure to get my app, maybe in less than a week, its called Feelit, we would be happy to have you aboard! xD –  Albara Sep 15 '13 at 13:31 
2  
This is one of the most beautiful solutions so far! Thanks! –  jovanjovanovic Feb 27 '14 at 4:00

uiimageview 异步加载图片的更多相关文章

  1. swift 异步加载图片(第三方框架ImageLoader)

    import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: ...

  2. swift 异步加载图片

    import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: ...

  3. UIImageView异步加载网络图片

    在iOS开发过程中,经常会遇到使用UIImageView展现来自网络的图片的情况,最简单的做法如下: 去下载https://github.com/rs/SDWebImage放进你的工程里,加入头文件# ...

  4. 多线程异步加载图片async_pictures

    异步加载图片 目标:在表格中异步加载网络图片 目的: 模拟 SDWebImage 基本功能实现 理解 SDWebImage 的底层实现机制 SDWebImage 是非常著名的网络图片处理框架,目前国内 ...

  5. 模仿SDWebImage实现异步加载图片

    模仿SDWebImage实现异步加载图片 SDWebImage想必大家都不陌生吧,要实现它的图片异步加载功能这个还是很简单的. 注意:此处我只实现了异步加载图片,并没有将文件缓存到本地的打算哦:) 源 ...

  6. ios UITableView 异步加载图片并防止错位

    UITableView 重用 UITableViewCell 并异步加载图片时会出现图片错乱的情况 对错位原因不明白的同学请参考我的另外一篇随笔:http://www.cnblogs.com/lesl ...

  7. IOS中UITableView异步加载图片的实现

    本文转载至 http://blog.csdn.net/enuola/article/details/8639404  最近做一个项目,需要用到UITableView异步加载图片的例子,看到网上有一个E ...

  8. 实例演示Android异步加载图片

    本文给大家演示异步加载图片的分析过程.让大家了解异步加载图片的好处,以及如何更新UI.首先给出main.xml布局文件:简单来说就是 LinearLayout 布局,其下放了2个TextView和5个 ...

  9. 实例演示Android异步加载图片(转)

    本文给大家演示异步加载图片的分析过程.让大家了解异步加载图片的好处,以及如何更新UI.首先给出main.xml布局文件:简单来说就是 LinearLayout 布局,其下放了2个TextView和5个 ...

随机推荐

  1. iOS NSURLConnection使用详解

    一.整体介绍 NSURLConnection是苹果提供的原生网络访问类,但是苹果很快会将其废弃,且由NSURLSession(iOS7以后)来替代.目前使用最广泛的第三方网络框架AFNetworkin ...

  2. pionter指针小结

    第七章.指针和函数的关系 可以把一个指针声明成为一个指向函数的指针. intfun1(char*,int); int(*pfun1)(char*,int); pfun1=fun1; .... .... ...

  3. python基础系列教程——Python库的安装与卸载

    python基础系列教程——Python库的安装与卸载 2.1 Python库的安装 window下python2.python3安装包的方法 2.1.1在线安装 安装好python.设置好环境变量后 ...

  4. Maven学习之(四)Maven插件创建web项目

    另一种maven web项目的创建. 创建出来的目录是这样的,此时试一下,不能加入到tomcat中去启动. 这里要将项目转化为web项目. 右键->项目 选中下面的动态web项目,然后OK 此时 ...

  5. zTree V3 是个好东西 功能很强大

    zTree V3 是个好东西 功能很强大 地址:http://www.ztree.me/v3/demo.php#_101

  6. mysql初步调试

    第一次这么近距离的接触源码,有点兴奋,又有点不知所措,尼玛随便一个源文件都有几千行,你说怎么看,还有一些数不清的数据结构,函数,类等乱七八糟的东西. 我装的mysql版本是5.7.12,然后运行平台是 ...

  7. mkyaffs2image编译

    http://blog.chinaunix.net/uid-26009923-id-3760474.htmlhttp://blog.csdn.net/xingtian19880101/article/ ...

  8. Mac安装wget

    Mac安装wget wget版本: wget-1.17 参考来源: Mac OS 安装Wget 給Mac添加wget功能 The Wget package for Mac http://brew.sh ...

  9. C#操作word类文件

    最近频繁操作Word文档,写了很多word的操作代码及方法,虽然已经有很多关于word的操作类了,自己还是进行了一下整合: 1.通过模板创建新文件 2.在书签处插入值 3.插入表格 4.合并单元格 5 ...

  10. C#程序运行流程

    我们写好的C#代码操作系统是如何能识别呢,我们都知道操作系统只能认识二进制 机器代码,下面用图来表示 因为C#是安全的托管代码 同时CLR会在内存中创建应用程序域 托管代码:被CLR管理的代码 非托管 ...