2011-12-03 31 views
3

我正在加载特定单元格的图像集,并且只有在等待所有图像加载后才能正常工作,我正在使用线程从Web获取图像问题出在何时在加载过程仍在进行时,我滚动表格视图,它会在错误的单元格位置生成图像。我知道这是错误的位置,因为我也为每个图像集设置了一个标签。任何帮助,将不胜感激提前感谢!滚动时在错误的UITableViewCell上加载图像

这里是示例代码。

- (void)viewDidLoad{ 
[super viewDidLoad]; 
/* loads xml from the web and parses the images and store it in core data(sqlite) 
    it uses NSFetchedController protocol for inserting cell after saving into core data 
*/ 
[self loadFromXmlSource]; 
} 

我有3个部分,使用故事板中的3个不同的原型单元格,我遇到的问题是照片部分。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath   *)indexPath 
{ 
Media *mediaModel = [_fetchedResultsController objectAtIndexPath:indexPath]; 
NSString *CellIdentifier = @"MediaCellNoImage"; 
if ([[mediaModel.category lowercaseString] isEqualToString:@"photos"]) { 
    CellIdentifier = @"MediaGalleryCell"; 
} else if ([[mediaModel.category lowercaseString] isEqualToString:@"videos"]) { 
    CellIdentifier = @"MediaCellNoImage"; 
} else if ([[mediaModel.category lowercaseString] isEqualToString:@"podcasts"]) { 
    CellIdentifier = @"MediaCell"; 
} 

MediaGalleryCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 
if (cell == nil) { 
    cell = [[MediaGalleryCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; 
} 
// I am also having a problem with image duplication so I always delete all images 
if([cell.reuseIdentifier isEqualToString:@"MediaGalleryCell"]) { 
    for(id subview in cell.scrollView.subviews){ 
     [subview removeFromSuperview]; 
    } 
} 

[self configureCell:cell atIndexPath:indexPath]; 
return cell; 
} 

这里是配置电池,我产生一个线程用于图像下载

-(void)configureCell:(MediaGalleryCell *)cell atIndexPath:(NSIndexPath *)indexPath 
{ 

Media *mediaModel = [_fetchedResultsController objectAtIndexPath:indexPath]; 
if ([[mediaModel.category lowercaseString] isEqualToString:@"photos"]) { 
     NSEnumerator *e = [mediaModel.attachments objectEnumerator]; 
     Attachment *attachment; 
     // iterate all images in a particular cell 
     while ((attachment = [e nextObject])) { 
       NSDictionary *objects = [[NSMutableDictionary alloc] initWithObjectsAndKeys: 

             attachment.identifier, @"identifier", 
             attachment.thumb, @"thumb", 
             attachment.filename, @"filename", 
             cell, @"cell", 
             nil]; 

       // for each image spawn a thread i am also passing the cell in order to set it there. 
       [NSThread detachNewThreadSelector:@selector(loadImageInScrollView:) 
             toTarget:self withObject:objects]; 
     } 
} else { 
    // code for other section 
} 


cell.titleLabel.text = mediaModel.title; 
cell.contentLabel.text = mediaModel.content; 
} 

这里是在线程的方法

-(void)loadImageInScrollView:(NSDictionary *)objects 
{ 
MediaGalleryCell *cell = [objects valueForKey:@"cell"]; 
SBCacheFile *cacheFile = [self getCache]; 
NSString *finalIdentifier = [NSString stringWithFormat:@"%@_s", [objects valueForKey:@"identifier"]]; 

// here is where I fetched the image and cache it locally 
UIImage *image = [cacheFile getCachedFileWithUrl:[objects valueForKey:@"thumb"] 
             withName:[objects valueForKey:@"filename"] 
             andStringIdentifier:finalIdentifier]; 

NSDictionary *obj = [[NSDictionary alloc] initWithObjectsAndKeys: 
        cell, @"cell", 
        image, @"image", 
        nil]; 
//after fetching the image I need to send it back to the main thread in order to do some UI operation 
[self performSelectorOnMainThread:@selector(setImageViewForGallery:) withObject:obj waitUntilDone:YES]; 

} 

这里是代码中的最后一块我添加图像作为一个单元格属性的子视图,它是一个scrollView,它像一个单元格中的图像旋转木马一样。

-(void)setImageViewForGallery:(NSDictionary *)dictionary 
{ 
MediaGalleryCell *cell = [dictionary valueForKey:@"cell"]; 
UIImage *image = [dictionary valueForKey:@"image"]; 

UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; 

UIImageView *lastImageView =[[cell.scrollView subviews] lastObject]; 
if (lastImageView != nil) { 
    [imageView setFrame:CGRectMake((lastImageView.frame.origin.x + lastImageView.frame.size.width + 10.0f), 
            5.0f, 70.0f, 70.0f)]; 
} else { 
    [imageView setFrame:CGRectMake(5.0f, 5.0f, 70.0f, 70.0f)]; 
} 

// This is where I add the imageView 
[cell.scrollView addSubview:imageView]; 

UIImageView *lastViewForSize = [[cell.scrollView subviews] lastObject]; 
    [cell.scrollView setContentSize:CGSizeMake(lastViewForSize.frame.origin.x + lastViewForSize.frame.size.width + 0.5,cell.scrollView.frame.size.height)]; 
} 

回答

3

首先,这是令人难以置信的危险:

// for each image spawn a thread i am also passing the cell in order to set it there. 
[NSThread detachNewThreadSelector:@selector(loadImageInScrollView:) 
         toTarget:self withObject:objects]; 

这可以派生一个巨大的(和无界)的线程数。可能发生的情况是,当你滚动时,线程已经完成了已经被重用的单元,并且跺下了他们的新数据。

你应该在这里做几件事情来改善事情。首先,你很少想使用detachNewThreadSelector:toTarget:withObject:。相反,使用一个NSOperationQueue,一个GCD队列或者只是一个工作人员NSThreadperformSelectorOnThread:...

接下来,您已经混合了您的模型和视图逻辑。您目前的方式是在视图(单元格)内存储模型数据(图像列表)。这在细胞重复使用时不起作用。视图不用于存储数据。

你应该有一个模型来跟踪所有的图像和他们去的地方。在你的情况下,这可能只是一个数组(行/图像)的数组。表视图数据源将始终返回一个单元格,该单元格包含数组相应行的当前内容。

每当模型更改时(因为下载了新图像),您的表视图委托应调用reloadRowsAtIndexPaths:withRowAnimation:以让表视图知道该行已更改。如果该行恰好需要,那么(并且只有这样)表视图才会向数据源请求一个新的单元,您将使用新图像进行更新。这种方法更清洁,更稳定,速度更快,并且使用更少的系统资源。

+0

谢谢你的答案是非常足智多谋,我想我需要研究这个NSOperationQueue等我是新来的ios。 –

1

这是UITableView的经典陷阱。当您将单元格滚动到视图外时,单元格将被重用以显示不同的数据。它基本上在顶部弹出,将填充新数据并重新出现在底部。

但是,您的后台进程不会收到通知。因此,当图像最终加载时,将其附加到已重用于不同的表格单元格。

因此,要解决这个问题,如果表格单元格不再可见,或者至少要通知线程表格单元格已被重用并且不再可能将图像分配一次它已经加载。当单元格滚动出视图或单元格被重用以显示不同的数据时,可能会发生这种情况。

在我的应用程序中,我使用了中央图像缓存。它最多使用四个线程来加载图像。并且它保留了所有表格单元的列表,用于等待要加载的图像。如果其中一个表格单元格出现在不同的图像请求中,则先前的请求会被修改,因此它不会更新表格单元格,而只是将图像放入中央缓存中。

+0

我明白这是有道理的,谢谢你的帮助! –

相关问题