2013-04-29 91 views
4

我有一个UICollectionView实现与自定义UICollectionViewCell。 数据的数组从互联网获取,但图像在用户访问内容时下载。GCD和细胞重用UICollectionView

虽然这可能不是最有效的方法,我不打算这样离开,但它确实暴露了我正在努力解决的某个问题。看起来这些单元格显示“缓存”或“旧”图像,当我缓慢滚动收集视图时,单元格图像不断变化。

这可能是一个问题,实际的细胞图像不可从网络在那个时刻和我的问题是我如何强制一个空单元格或一些加载活性监视器,而不是显示不正确的图像?

在此先感谢,小

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 
{ 
static NSString * CellIdentifier = @"Event"; 

EventCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath]; 


NSString *title = [[events objectAtIndex:indexPath.item] objectForKey:@"title"]; 
NSString *imageUrl = [[[events objectAtIndex:indexPath.item] objectForKey:@"photo"] objectForKey:@"url"]; 


dispatch_queue_t imageFetchQ = dispatch_queue_create("image fetched", NULL); 
dispatch_async(imageFetchQ, ^{ 

    NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]; 

    if (imageData) 
    { 

     dispatch_async(dispatch_get_main_queue(), ^{ 

      cell.eventImage.image = [UIImage imageWithData:imageData]; 
      cell.eventTitle.text = title; 

     }); 
    } 
}); 


return cell; 

} 
+0

我会建议你使用NSOperationQueue来代替,就像本教程中的内容: http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues – 2013-04-29 08:12:04

+0

我试过按照教程,但它并没有真正处理重用问题。我看到NSOperationQueue将适用于这种解决方案,但问题依然存在。我想我需要实现某种机制来验证哪个图像即将显示在每个单元格中。不知道如何。然而。 – Pinion 2013-05-02 23:23:43

回答

4

这是一个常见的问题。最大的问题是,当细胞的图像到达时,细胞可能已被回收并可能用于其他物品。如果您尝试为单元格设置图像,那么图像最终可用,因此您很可能会为错误的项目设置图像。

所以,不要试图直接更新单元格。甚至不要在图像下载完成块中保留对单元格的引用。相反,让完成块更新数据模型,无论如何。您也可以保留对集合视图的弱引用,并存储单元格的索引路径。当图像到达时,完成代码可以调用-reloadItemsAtIndexPaths:,如果单元仍然可见,这将导致集合视图为受影响的索引路径重新加载单元。你的-collectionView:cellForItemAtIndexPath:方法只是做它通常的事情,但这次图像将在数据模型中可用。

所以,你的代码看起来是这样的:

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 
{ 
    static NSString * CellIdentifier = @"Event"; 
    EventCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath]; 

    Event *event = [events objectAtIndex:indexPath.item]; // replace "Event" with whatever class you use for your items 
    cell.eventTitle.text = [event objectForKey:@"title"]; 
    cell.eventImage.image = [event objectForKey:@"image"]; 
    if (cell.eventImage.image == nil) { 
     NSString *imageUrl = [[[events objectAtIndex:indexPath.item] objectForKey:@"photo"] objectForKey:@"url"]; 

     dispatch_queue_t imageFetchQ = dispatch_queue_create("image fetched", NULL); 
     dispatch_async(imageFetchQ, ^{ 
      __weak UICollectionView *weakCollection; 
      NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]; 
      UIImage *image = [UIImage imageWithData:imageData]; 
      if (image) 
      { 
       dispatch_async(dispatch_get_main_queue(), ^{ 
        [event setObject:image forKey:@"image"]; // updating the model here 
        [weakCollection reloadItemsAtIndexPaths:@[indexPath]]; 
       }); 
      } 
     }); 
    } 
    return cell; 
} 

我没有测试过这个确切的代码,因为我不知道你的数据模型是什么样子。请确保将我自己假设的Event类替换为您自己的数据项类。但是,我在自己的代码中使用了相同的技术,我认为这是正确的方法。亮点:

  • 更新数据模型中的图像意味着,这将是下一个可用的时间的项目需要显示,即使图像被下载时,该项目是不可见的。
  • 对集合视图使用弱引用可以避免在用户导航离开集合视图时图像仍然到达时出现问题。
  • 调用-reloadItemsAtIndexPaths:有助于将对单元格的更改限制为您的...cellForItemAtIndexPath:方法。

一个需要注意的是,如果你显示可以更改(例如,如果用户可以添加或删除随着时间的推移项目)事情的清单,然后甚至有可能不会是安全的为您完成代码依靠索引路径保持不变。在这种情况下,您需要在图像到达时确定项目的索引路径。