2013-10-30 108 views
9

即时通讯使用异步块(大中央调度)加载我的细胞图像。但是,如果您快速滚动,它们仍会以非常快的速度出现,直到它加载了正确的一个。我确定这是一个普遍的问题,但我似乎无法找到它。tableView细胞图像异步加载闪烁快速滚动

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
static NSString *CellIdentifier = @"Cell"; 
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 


// Load the image with an GCD block executed in another thread 
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 

    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[[appDelegate offersFeeds] objectAtIndex:indexPath.row] imageurl]]]; 

    dispatch_async(dispatch_get_main_queue(), ^{ 

     UIImage *offersImage = [UIImage imageWithData:data]; 

     cell.imageView.image = offersImage; 
    }); 
}); 

cell.textLabel.text = [[[appDelegate offersFeeds] objectAtIndex:indexPath.row] title]; 
cell.detailTextLabel.text = [[[appDelegate offersFeeds] objectAtIndex:indexPath.row] subtitle]; 

return cell; 
} 
+0

请参阅http://stackoverflow.com/questions/19326284/what-is-the-proper-way-to-use-nscache-with-dispatch-async-in-a-reusable-table-ce/19327472# 19327472或http://stackoverflow.com/questions/16663618/async-image-loading-from-url-inside-a-uitableview-cell-image-changes-to-wrong/16663759#16663759 – Rob

+0

请考虑我的帖子http ://stackoverflow.com/a/26147814/305205​​9。谢谢! –

+0

我会推荐阅读这篇博文http://www.natashatherobot.com/how-to-download-images-asynchronously-and-make-your-uitableview-scroll-fast-in-ios/ – pprochazka72

回答

17

最起码,你可能想在你dispatch_async从细胞中删除图像(如果它是一个重新使用的细胞):

cell.imageView.image = [UIImage imageNamed:@"placeholder.png"]; 

或者

cell.imageView.image = nil; 

您还希望在更新之前确保问题单元仍在屏幕上(使用UITableView方法,cellForRowAtIndexPath:,如果该单元格返回nil升该行不再可见,不与UITableViewDataDelegate方法tableView:cellForRowAtIndexPath:),例如:

static NSString *CellIdentifier = @"Cell"; 
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 

cell.imageView.image = [UIImage imageNamed:@"placeholder.png"]; 

// Load the image with an GCD block executed in another thread 
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 

    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[[appDelegate offersFeeds] objectAtIndex:indexPath.row] imageurl]]]; 

    if (data) { 
     UIImage *offersImage = [UIImage imageWithData:data]; 
     if (offersImage) { 
      dispatch_async(dispatch_get_main_queue(), ^{ 
       UITableViewCell *updateCell = [tableView cellForRowAtIndexPath:indexPath]; 

       if (updateCell) { 
        updateCell.imageView.image = offersImage; 
       } 
      }); 
     } 
    } 
}); 

cell.textLabel.text = [[[appDelegate offersFeeds] objectAtIndex:indexPath.row] title]; 
cell.detailTextLabel.text = [[[appDelegate offersFeeds] objectAtIndex:indexPath.row] subtitle]; 

return cell; 

坦白混淆,应还可以使用高速缓存以避免重新检索不必要的图像(例如您向下滚动一下并向上滚动,您不想针对那些之前的单元格图像发出网络请求)。更好的是,您应该使用UIImageView类别之一(例如SDWebImageAFNetworking中包含的类别)。这实现了异步图像加载,但也优雅地处理缓存(不要恢复您刚刚检索的图像几秒钟前),取消尚未发生的图像(例如,如果用户快速滚动到第100行,你可能不会在向用户展示第100行的图像之前,不想等待第99个检索点)。

+0

还有一个潜力在异步调用完成之前单元可能被重用的问题,导致正确的映像被前一个调用中的映像替换。异步加载tableview单元格的图像不是微不足道的。 – kubi

+0

@ kubi同意,但我的代码示例通过检查'updateCell'来解决该问题,以确保单元不被重用。但是你说得对,它不是微不足道的,我对即时问题的战术解决方案没有解决各种其他问题(缓存,积压的请求等),因此我建议使用其中一个“UIImageView”类别,这使它更容易。 – Rob

+0

@Rob,我会很高兴,如果你看看下面的问题http://stackoverflow.com/questions/33397726/download-i-the-collectionview – hotspring

0

问题是您在主线程下载图像。派遣背景队列为:

dispatch_queue_t myQueue = dispatch_queue_create("myQueue", NULL); 

    // execute a task on that queue asynchronously 
    dispatch_async(myQueue, ^{ 
     NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[[appDelegate offersFeeds] objectAtIndex:indexPath.row] imageurl]]]; 
//UI updates should remain on the main thread 
UIImage *offersImage = [UIImage imageWithData:data]; 
    dispatch_async(dispatch_get_main_queue(), ^{ 

     UIImage *offersImage = [UIImage imageWithData:data]; 

     cell.imageView.image = offersImage; 
    }); 
    }); 
+0

user2920762未在主线程上下载图像。他使用全局(即后台)队列(这不是很好,但比主队列更好)。他根本没有正确处理单元的重用(也没有正确地处理单元在异步检索完成时可能滚动到屏幕外的概念)。 – Rob

+0

'UIImage * offersImage = [UIImage imageWithData:data];'这是在他的代码中的主线程上执行的。 –

+0

同意。 (除此之外,你实际上正在做'imageWithData'两次,一次在背景队列中,一次在主队列中,我相信你不打算这么做。)主要的问题是在后台队列上进行检索只要他在下载新图像时看到旧图像(并且不处理在图像被下载时该单元可能被重新用于另一行的事实)。所以,你是对的,你应该在后台队列中执行'imageWithData',但这并不能解决问题。问题是在派发之前未能初始化'cell.imageView.image'。 – Rob

1

这与异步图像加载一个问题... 比方说,你在任何给定时间5个可见行。 如果您快速滚动,并向下滚动例如10行,tableView:cellForRowAtIndexPath将被调用10次。问题是这些调用比图像返回更快,并且您有来自不同URL的10个待处理图像。 当图像最终从服务器返回时,它们将在放入异步加载程序的单元上返回。由于您只重复使用5个单元格,因此这些图像中的一些将在每个单元格上显示两次,因为它们是从服务器下载的,这就是您看到闪烁的原因。另外,记得打电话

cell.imageView.image = nil

调用异步下载方法之前,因为从重用单元前面的图像将保留,也造成闪烁效果的新形象被分配时。

解决方法是存储要在每个单元格上显示的图像的最新URL,然后当图像从服务器返回时,请检查该URL与请求中的URL。如果不相同,请稍后缓存该图像。 对于缓存请求,请查看NSURLRequestNSURLConnection类。

我强烈建议您使用AFNetworking进行任何服务器通信。

祝你好运!

2

你的闪烁的原因是你在滚动过程中开始下载多个图像,每当你在屏幕上显示一个单元格时会执行一个新的请求,并且可能旧的请求没有完成,请求完成图像在单元格上设置,所以它是如果您快速滚动您使用让我们说一个单元格3次= 3个将被触发的请求= 3个图像将设置在该单元格=闪烁。

我有同样的问题,这里是我的方法: 创建一个自定义单元格与所有必需的意见。每个单元都有自己的下载操作。在单元的-prepareForReuse方法中。我会让图像零并取消请求。

这样对于每个单元我只有一个请求操作=一个图像=没有闪烁。

即使使用AFNetworking如果您不取消图像下载,您也可能遇到同样的问题。

+0

我设置了imageview,image = nil,它完成了这项工作! – Elsammak

相关问题