34

GCD を使用して uitableview の画像を非同期でダウンロードしていますが、問題があります。画像をスクロールするとちらつき、常に変化します。すべてのセルでイメージを 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) {

                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;
}
4

8 に答える 8

97

ここでの問題は、画像フェッチ ブロックがテーブルビュー セルへの参照を保持していることです。ダウンロードが完了すると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;
}
于 2013-03-27T20:05:03.623 に答える
7

ポイントは、セルの再利用の概念を十分に理解していないということです。これは、非同期ダウンロードとはあまり一致しません。

ブロック

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

リクエストが終了し、すべてのデータがロードされたときに実行されます。ただし、ブロックが作成されると、セルはその値を取得します。

ブロックが実行されるまで、セルはまだ既存のセルの 1 つを指しています。しかし、ユーザーがスクロールを続けた可能性は十分にあります。セル オブジェクトはその間に再利用され、画像は再利用され、割り当てられて表示される'古い' セルに関連付けられています。その直後に、ユーザーがさらにスクロールしない限り、正しい画像が読み込まれ、割り当てられて表示されます。などなど。

それを行うためのよりスマートな方法を探す必要があります。たくさんのトゥロリアルがあります。遅延画像読み込み用の Google。

于 2013-03-27T20:15:14.183 に答える
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;
}
于 2013-03-27T21:13:46.953 に答える
1

車輪を再発明するのではなく、次のいくつかの素晴らしいポッドを使用してください。

https://github.com/rs/SDWebImage

https://github.com/JJSaccolo/UIActivityIndi​​cator-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];
于 2015-12-17T22:30:32.557 に答える
1

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)
              }
          });
     }
于 2016-10-26T06:47:24.560 に答える
1

SDWebImage の使用を考えたことはありますか? また、指定された URL から非同期的に画像をダウンロードします。ライブラリ全体は使用しませんでした (UIImageView+WebCache.h のみをインポートしました)。インポートしたら、次のようにメソッドを呼び出すだけです。

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

AFNetworking 2.0 を使用している場合はやり過ぎかもしれませんが、私にとってはうまくいきました。

試してみたい場合は、githubへのリンクを次に示します

于 2014-06-27T08:00:43.483 に答える