2012-07-23 221 views
4

我實現了UIImageView單元的UITableView,每個單元通過NSTimer每隔5秒定期刷新一次。每個圖像在後臺線程中從服務器加載,並且從後臺線程中,我還通過調用performSelectorOnMainThread來更新UI,顯示新圖像。到現在爲止還挺好。iOS:如何在主線程中更新UI的後臺線程?

當我不等到一個圖像加載到當前單元格並且我快速向下滾動到一個新的單元格時,我目前遇到了一個問題。然而,這個新單元顯示的是前一個單元的圖像,而不是新單元的圖像。雖然下一輪的NSTimer會正確顯示圖像,但這可能會首先讓用戶感到困惑。

如果我不重用UITableView的單元格,問題就會消失,但如果在我的應用程序中顯示單元格的數量,這不是一個選項。

所以我能想到的唯一解決方案是取消(或殺死)後臺線程,如果我知道用戶執行滾動操作,將顯示舊圖像。

我想知道這可能不是最佳實踐,因此,尋求您的建議。

// In MyViewController.m 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    ... 
    NSTimer* timer=[NSTimer scheduledTimerWithTimeInterval:ANIMATION_SCHEDULED_AT_TIME_INTERVAL 
               target:self 
               selector:@selector(updateImageInBackground:) 
               userInfo:cell.imageView 
               repeats:YES]; 
    ... 
} 

- (void) updateImageInBackground:(NSTimer*)aTimer 
{ 
    [self performSelectorInBackground:@selector(updateImage:) 
         withObject:[aTimer userInfo]]; 
} 

- (void) updateImage:(AnimatedImageView*)animatedImageView 
{  
    @autoreleasepool { 
     [animatedImageView refresh]; 
    } 
} 

// In AnimatedImageView.m 
-(void)refresh 
{ 
    if(self.currentIndex>=self.urls.count) 
     self.currentIndex=0; 

    ASIHTTPRequest *request=[[ASIHTTPRequest alloc] initWithURL:[self.urls objectAtIndex:self.currentIndex]]; 
    [request startSynchronous]; 

    UIImage *image = [UIImage imageWithData:[request responseData]]; 

    // How do I cancel this operation if I know that a user performs a scrolling action, therefore departing from this cell. 
    [self performSelectorOnMainThread:@selector(performTransition:) 
         withObject:image 
        waitUntilDone:YES]; 
} 

-(void)performTransition:(UIImage*)anImage 
{ 
    [UIView transitionWithView:self duration:1.0 options:(UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowUserInteraction) animations:^{ 
     self.image=anImage; 
     currentIndex++; 
    } completion:^(BOOL finished) { 
    }]; 
} 
+1

WWDC 2012會話211詳細介紹了這一點,其中包括不同級別的隊列取消,優化等。這是一個很棒的視頻。 'cellForRow'中的 – jrturton 2012-07-23 09:54:58

+0

,爲什麼不立即設置圖像爲零?這樣,它會暫時空白,而不是錯誤的圖像。 – user102008 2012-07-23 17:55:35

+0

@ user102008我這樣做了,但它不起作用。錯誤的圖像已經在主線程中排隊(刷新處於執行過程中),等待顯示。 – 2012-07-23 22:05:49

回答

1

好(我,因爲我的要求就是顯示一組從服務器中加載循環圖片也不能使用SDWebImage)... 另一種方式來做到這將是把您的「形象請求代碼」到你的AnimatedImageView,並失去每次設定一個新的圖像URL時掛起的請求

// your controller 
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
    { 
    ... 
    [cell.imageView setDistantImage:imageURL]; 
    ...  
    } 

然後,在你的手機類,「setDistantImage」創建ASIHttpRequest和無效前一個

//your ImageView class 

@property (...) ASIHttpRequest *distantImageRequest; 

- (void) setDistantImageUrl:(NSUrl *)imageUrl 
{ 
    //clear previous request 
    [distantImageRequest clearDelegatesAndCancel]; 
    //set a new request, with the callBack embedded directly in this ImageView class 
    ... 
} 

//animation and callBacks methods in this ImageView class too... 
0

我的CollectionView & TableView中有重用的小區覆蓋所有具有快速和長期滾動下載的圖像加載許多圖像時有完全相同的問題。

對於TableView,我解決了它在'didEndDisplayingCell'方法出現的地方看WWDC 2012,但是這對於沒有這種方法的CollectionView來說並不是有用的。 :O(

最後,我發現了兩個......幸運的是解決方案 的想法是創建ImageLoader的從TableView中或的CollectionView和細胞叫,這將是負責。下載圖像 在您的實現,添加這段代碼隱藏變量到世界其他地區:

@interface ImageLoader() { 
__block NSURLSession *_session; 
} 

然後,添加下列功能的實現:

- (void)getViewForCell:(UIImageView*)cell 
       FromURL:(NSURL*)url { 

NSBlockOperation *_loadImageOp; 
__block NSOperationQueue *_opQueue; 

_opQueue = [[NSOperationQueue alloc]init]; 

if (_session != nil) { 
    [_session invalidateAndCancel]; 
} 

_loadImageOp = [[NSBlockOperation alloc]init]; 
[_loadImageOp addExecutionBlock:^{ 

    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; 
    _session = [NSURLSession sessionWithConfiguration:config 
              delegate:nil 
             delegateQueue:_opQueue]; 

    NSURLSessionDownloadTask *downloadTask = [_session downloadTaskWithURL:url 
                 completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { 
     UIImage *imageCell = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]]; 

     if (imageCell) { 
      [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 
       cell.image = imageCell; 
      }]; 
     } else { 
      [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 
       cell.image = [UIImage imageNamed:@"imageNotWorking.png"]; 
      }]; 
     } 
    }]; 

    [downloadTask resume]; 
}]; 

[_opQueue addOperation:_loadImageOp]; 
} 

這個想法是1 /。您創建一個塊,其中成員變量_session將通過downloadTask,2 /獲取傳入參數中給定的url。在下載時通過mainQueue將圖像顯示到用戶界面... 3 /。最初創建的塊被添加到專用於該特定重用單元的隊列中。

重要的是'invalidateAndCancel'函數被調用,如果成員變量不是零,因爲每次你想重用單元格時,前一個會話將不會返回最後一個圖像,而是新定義的傳入參數'url'。

然後,不要忘記在您的TableView/CollectionView單元類中放置1 /。以下代碼片段,以便爲每個重複單元使用一個imageLoader和2 /要在方法中調用的方法'的tableView:的cellForRowAtIndexPath:' 你的CollectionView的/ TableView中:

@interface TableCell() { //...or CollectionCell :o) 
ImageLoader *_imgLoader; 
} 
@end 

@implementation TableCell 
- (instancetype)initWithCoder:(NSCoder *)aDecoder { 
self = [super initWithCoder:aDecoder]; 
_imgLoader = [[ImageLoader alloc]init]; 
return self; 
} 

- (void)imageURL:(NSURL*)url { 
[_imgLoader getViewForCell:self.imageViewWhereTheDownloadedImageIs 
        FromURL:url]; 
} 
@end 

一旦完成,只需調用該方法[cell imageURL:url] '的tableView:的cellForRowAtIndexPath:' 你的CollectionView/TableView中的。

現在,您可以儘可能快地滾動,並且始終具有適當的圖像,而且只有這一個。

我希望它能幫助...很好。 :o)