2011-12-03 32 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

我明白這是有道理的,謝謝你的幫助! –

相關問題