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. java华为面试题

    JAVA方面 1 面向对象的特征有哪些方面 2 String是最基本的数据类型吗? 3 int 和 Integer 有什么区别 4 String 和StringBuffer的区别 5运行时异常与一般异 ...

  2. 一款基于jquery和css3的响应式二级导航菜单

    今天给大家分享一款基于jquery和css3的响应式二级导航菜单,这款导航是传统的基于顶部,鼠标经过的时候显示二级导航,还采用了当前流行的响应式设计.效果图如下: 在线预览   源码下载 实现的代码. ...

  3. JavaScrip——练习(做悬浮框进一步:悬浮窗后缀悬浮窗【感觉这种方法比较麻烦】)

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  4. Win2008 Server系统安装打印服务器与配置

    原文:http://60808.org/thread-20259-1-1.html 视频地址:http://edu.51cto.com/lesson/id-20163.html 本文介绍的是在Win2 ...

  5. 清除DataGridView显示的数据

    一.DataGridView未绑定数据时清空数据 this.dgv_PropDemo.DataSource = null 二.DataGridView绑定数据时清空数据 DataGridView绑定了 ...

  6. Python time & datetime & string 相互转换

    #!/usr/bin/env python# -*- coding:utf-8 -*- # @Datetime : 2017/11/23 下午12:37# @Author : Alfred Xue# ...

  7. 关闭 禁用 Redis危险命令

    Redis的危险命令主要有: flushdb,清空数据库 flushall,清空所有记录,数据库 config,客户端连接后可配置服务器 keys,客户端连接后可查看所有存在的键 我们常常需要禁用以上 ...

  8. cs108 04 oop design

    oop design 分为以下几个方面: - encapsulation and modularity(封装和模块化) - API/Client interface design(API 接口给调用类 ...

  9. Framework 7 日历插件改成Picker 模式

    Framework 7 里面的日历插件默认的2种模式: 1.文本框 2.直接展示 如下图: 更多例子点这里 而我的需求如下图: 点击小图标再弹出日历,选择某个日期,隐藏日历弹层. 实现步骤: 1.写小 ...

  10. jQuery的Cookie操作插件

    jQuery的cookie插件 01 // jQuery.cookie.js 02 jQuery.cookie = function(name, value, options) { 03     if ...