2013-03-27 14 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

ここでの問題は、あなたのイメージフェッチブロックは、テーブルビューのセルへの参照を保持しているということです。ダウンロードが完了すると、別の行を表示するためにセルをリサイクルした場合でも、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'ことができませんでしたか?その場合、タグチェックは不適切に渡されます。 –

+0

はい、上記のコードは1セクションのテーブルを前提としています。 –

+0

@SeamusCampbell、マルチセクションテーブルの考え: –

7

ポイントは、あなたが完全にコンセプトを再利用し、セルを理解していなかったということです。これは、非同期ダウンロードとよく一致しません。

ブロック

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

要求が完了すると、すべてのデータがロードされたときに実行されます。しかし、ブロックが作成されると、セルはその価値を得ます。

ブロックが実行される時点までに、セルはまだ既存のセルの1つを指しています。しかし、ユーザーがスクロールを続ける可能性は非常に高いです。その間にセルオブジェクトが再利用され、画像は再利用され、割り当てられて表示される ''セルに関連付けられます。その後すぐに、ユーザーがさらにスクロールしない限り、正しいイメージがロードされ、割り当てられて表示されます。等々。

あなたはそれを行うよりスマートな方法を探してください。多くの洞窟があります。怠惰な画像読み込みのためのGoogle。

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'の呼び出しがセルのために呼び出される可能性があります。 –

+0

@AndrewRobinson高速スクロールで動作しない場合、動作しません。しかし、私はあなたが記述する方法でこのアプローチが失敗するのを見たことはありませんでした。ブロックを実行している間に別のインデックスパスでセルが再利用されたとどのように判断しましたか? –

+0

@AndrewRobinsonもし私が調査したいと思っていたgithubや何かの上でサンプルプロジェクトを投稿できたら、残念ながら私の頭の上から洞察を得ることはできませんが、あなたのプロジェクトを見過ごしています。 –

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のための2つのプロパティを宣言し、のviewDidLoadでそれらを初期化します。あなたのデータソースがNSMutableDictionaryの配列(またはNSManagedObjectContext)である

@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。次のように、各セルの画像を簡単にダウンロードできます。

. 
. 
. 
- (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; 
} 

デベロッパーに役立つことを願っています。 乾杯。

CREDITS:Table View Images in iOS 7

2

だけで素晴らしいポッドのこのカップルを使用して、車輪の再発明をしないでください:

[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

1
:ような単純な

https://github.com/rs/SDWebImage

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

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) 
       } 
      }); 
    } 
関連する問題