2013-03-27 45 views
30

我正在使用GCD異步下載我的uitableview的圖像,但存在一個問題 - 當滾動圖像閃爍並且始終更改時。我試圖將圖像設置爲零,但沒有太大的幫助。 快速滾動備份時,所有圖像都是錯誤的。我能做些什麼? 這裏是我的細胞的方法:使用GCD異步下載UITableView的圖像

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

    if (self.loader.parsedData[indexPath.row] != nil) 
    { 
     cell.imageView.image = nil; 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 
      dispatch_async(queue, ^(void) { 

       NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self.loader.parsedData[indexPath.row] objectForKey:@"imageLR"]]]; 

       UIImage* image = [[UIImage alloc] initWithData:imageData]; 

       dispatch_async(dispatch_get_main_queue(), ^{ 
        cell.imageView.image = image; 
        [cell setNeedsLayout]; 
        }); 
      }); 

    cell.textLabel.text = [self.loader.parsedData[indexPath.row] objectForKey:@"id"]; 
    } 
    return cell; 
} 

回答

89

這裏的問題是,你的圖像獲取塊都抱着到的tableview細胞引用。下載完成後,即使您已經回收單元以顯示不同的行,它也會設置imageView.image屬性。

在設置圖像之前,您需要下載完成塊來測試圖像是否與單元格相關。

另外值得注意的是,您不是將圖像存儲在單元格以外的任何地方,因此每次在屏幕上滾動一行時都會再次下載它們。在開始下載之前,您可能想將它們緩存在某處並查找本地緩存的圖像。

編輯:這裏有一個簡單的方法來測試,利用細胞tag屬性:

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

    cell.tag = indexPath.row; 
    NSDictionary *parsedData = self.loader.parsedData[indexPath.row]; 
    if (parsedData) 
    { 
     cell.imageView.image = nil; 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 
     dispatch_async(queue, ^(void) { 

      NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:parsedData[@"imageLR"]]; 

      UIImage* image = [[UIImage alloc] initWithData:imageData]; 
      if (image) { 
       dispatch_async(dispatch_get_main_queue(), ^{ 
        if (cell.tag == indexPath.row) { 
         cell.imageView.image = image; 
         [cell setNeedsLayout]; 
        } 
       }); 
      } 
     }); 

     cell.textLabel.text = parsedData[@"id"]; 
    } 
    return cell; 
} 
+0

@SeamusCampbell,不能'indexPath.row'是重用電池相同的,如果它是在不同的部分?在這種情況下,標籤檢查將不適當地通過。 – 2014-02-13 20:16:46

+0

是的,上面的代碼假設了一個單節表格。 – 2014-02-13 20:48:56

+0

@SeamusCampbell,對於多節表的任何想法:) – 2014-02-15 20:31:47

7

的一點是,你沒有完全瞭解電池重複使用的概念。這與異步下載不太一致。

^{ 
    cell.imageView.image = image; 
    [cell setNeedsLayout]; 
} 

得到當請求完成後,所有的數據被加載執行的塊。但是單元在創建塊時會獲得它的價值。

在塊執行時,單元仍指向其中一個現有單元。但用戶很可能繼續滾動。在此期間重新使用了單元對象,並且該圖像與重用並分配並顯示的''單元相關聯。不久之後,正確的圖像將被加載並分配並顯示,除非用戶進一步滾動。等等等等。

你應該尋找一個更聰明的方式來做到這一點。有很多turorials。谷歌爲懶惰圖像加載。

6

使用索引路徑獲取單元格。如果它不可見,則單元格將爲nil,並且您不會遇到問題。當然,您可能需要在下載數據時緩存數據,以便在已經有圖像時立即設置單元格的圖像。

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

    if (self.loader.parsedData[indexPath.row] != nil) 
    { 
     cell.imageView.image = nil; 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 
      dispatch_async(queue, ^(void) { 
       // You may want to cache this explicitly instead of reloading every time. 
       NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self.loader.parsedData[indexPath.row] objectForKey:@"imageLR"]]]; 
       UIImage* image = [[UIImage alloc] initWithData:imageData]; 
       dispatch_async(dispatch_get_main_queue(), ^{ 
        // Capture the indexPath variable, not the cell variable, and use that 
        UITableViewCell *blockCell = [tableView cellForRowAtIndexPath:indexPath]; 
        blockCell.imageView.image = image; 
        [blockCell setNeedsLayout]; 
       }); 
      }); 
     cell.textLabel.text = [self.loader.parsedData[indexPath.row] objectForKey:@"id"]; 
    } 

    return cell; 
} 
+0

只需注意,這不適用於非常快速的滾動。仍然可以看到圖像添加到不正確的單元格。如果在重用單元之前調用'[tableView cellForRowAtIndexPath:indexPath]',重用單元格後可能會調用'blockCell.imageView.image = image'。 – 2014-02-13 20:05:40

+0

@AndrewRobinson如果它不適用於快速滾動,那麼它不起作用。但是,我還沒有看到這種方法會以您描述的方式失敗,我自己 - 您是否可以詳細說明如何確定單元在執行塊時是否被重新用於不同的索引路徑? – 2014-02-15 15:11:20

+0

@AndrewRobinson如果可以在github上發佈一個示例項目或者我可以玩的東西,我也願意調查;不幸的是,除了看到你的項目之外,我還沒有看清楚自己的頭腦。 – 2014-02-16 14:21:38

1

您是否想過使用SDWebImage?它也異步地從給定的URL下載圖像。我沒有使用整個庫(僅導入UIImageView + WebCache.h)。導入後,所有你需要做的就是調用該方法,例如:

[UIImageXYZ sd_setImageWithURL:["url you're retrieving from"] placeholderImage:[UIImage imageNamed:@"defaultProfile.jpeg"]]; 

如果你使用AFNetworking 2.0這可能是矯枉過正,但它的工作對我來說。

Here is the link to the github if you want to give it a try

3

我一直在研究這個問題,我發現通過自定義的UITableViewCell的一個很好的辦法。現在

#import <UIKit/UIKit.h> 

@interface MyCustomCell : UITableViewCell 

@property (nonatomic, strong) NSURLSessionDataTask *imageDownloadTask; 
@property (nonatomic, weak) IBOutlet UIImageView *myImageView; 
@property (nonatomic, weak) IBOutlet UIActivityIndicatorView *activityIndicator; 

@end 

,在你TableViewController,爲NSURLSessionConfiguration和NSURLSession聲明兩個屬性和viewDidLoad中初始化它們:

@interface MyTableViewController() 

@property (nonatomic, strong) NSURLSessionConfiguration *sessionConfig; 
@property (nonatomic, strong) NSURLSession *session; 
. 
. 
. 
@end 

@implementation TimesVC 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; 
    _session = [NSURLSession sessionWithConfiguration:_sessionConfig]; 
} 

. 
. 
. 

讓我們supose你的數據源是一個的NSMutableDictionary的陣列(或的NSManagedObjectContext)。您可以下載easilly圖像的每個細胞,與緩存,這樣的:

. 
. 
. 
- (MyCustomCell *)tableView:(UITableView *)tableView 
    cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    MyCustomCell *cell = (MyCustomCell *)[tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; 

    if (!cell) 
    { 
     cell = [[MyCustomCell alloc] initWithStyle:UITableViewCellStyleDefault 
           reuseIdentifier:@"cell"]; 
    } 

    NSMutableDictionary *myDictionary = [_myArrayDataSource objectAtIndex:indexPath.row];  

    if (cell.imageDownloadTask) 
    { 
     [cell.imageDownloadTask cancel]; 
    } 

    [cell.activityIndicator startAnimating]; 
    cell.myImageView.image = nil; 

    if (![myDictionary valueForKey:@"image"]) 
    { 
     NSString *urlString = [myDictionary valueForKey:@"imageURL"]; 
     NSURL *imageURL = [NSURL URLWithString:urlString]; 
     if (imageURL) 
     { 
      cell.imageDownloadTask = [_session dataTaskWithURL:imageURL 
       completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) 
      { 
       if (error) 
       { 
        NSLog(@"ERROR: %@", error); 
       } 
       else 
       { 
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; 

        if (httpResponse.statusCode == 200) 
        { 
         UIImage *image = [UIImage imageWithData:data]; 

         dispatch_async(dispatch_get_main_queue(), ^{ 
          [myDictionary setValue:data forKey:@"image"]; 
          [cell.myImageView setImage:image]; 
          [cell.activityIndicator stopAnimating]; 
         }); 
        } 
        else 
        { 
         NSLog(@"Couldn't load image at URL: %@", imageURL); 
         NSLog(@"HTTP %d", httpResponse.statusCode); 
        } 
       } 
      }]; 

      [cell.imageDownloadTask resume]; 
     } 
    } 
    else 
    { 
     [cell.myImageView setImage:[UIImage imageWithData:[myDictionary valueForKey:@"image"]]]; 
     [cell.activityIndicator stopAnimating]; 
    } 

    return cell; 
} 

我希望它可以幫助一些開發! 乾杯。

鳴謝:Table View Images in iOS 7

2

不要推倒重來,只需使用這對夫妻真棒莢:

https://github.com/rs/SDWebImage

https://github.com/JJSaccolo/UIActivityIndicator-for-SDWebImage

簡單:

[self.eventImage setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", [SchemeConfiguration APIEndpoint] , _event.imageUrl]] 
          placeholderImage:nil 
           completed:^(UIImage *image, 
              NSError *error, 
              SDImageCacheType cacheType, 
              NSURL *imageURL) 
    { 
     event.image = UIImagePNGRepresentation(image); 
    } usingActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; 
+0

這是在沒有第三方庫的情況下實施這個訪問約束。 – Dvole 2015-12-18 05:45:47

1

旁邊接受了a Seamus Campbell你應該知道,有時這是行不通的。在這種情況下,我們應該重新加載特定的單元格。所以

if (image) { 
    dispatch_async(dispatch_get_main_queue(), ^{ 
      if (cell.tag == indexPath.row) { 
       cell.imageView.image = image; 
       [cell setNeedsLayout]; 
      } 
     }); 
} 

應改爲,

if (image) { 
     dispatch_async(dispatch_get_main_queue(), ^{ 
       if (cell.tag == indexPath.row) { 
        cell.imageView.image = image; 
        self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None) 
       } 
      }); 
    }