[解決済み] UITableViewのセル内にurlから非同期で画像を読み込むと、スクロール中に画像が間違った画像に変わってしまう
質問
UITableViewのセル内に画像を非同期でロードする方法を2つ書きました。どちらの場合も、画像はうまくロードされますが、テーブルをスクロールすると、画像が数回変わり、スクロールが終わると画像は正しい画像に戻ります。なぜこのようなことが起こるのか、全く分かりません。
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL: [NSURL URLWithString:
@"http://myurl.com/getMovies.php"]];
[self performSelectorOnMainThread:@selector(fetchedData:)
withObject:data waitUntilDone:YES];
});
}
-(void)fetchedData:(NSData *)data
{
NSError* error;
myJson = [NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions
error:&error];
[_myTableView reloadData];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
// Return the number of rows in the section.
// Usually the number of items in your array (the one that holds your list)
NSLog(@"myJson count: %d",[myJson count]);
return [myJson count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (cell == nil) {
cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
dispatch_async(kBgQueue, ^{
NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];
dispatch_async(dispatch_get_main_queue(), ^{
cell.poster.image = [UIImage imageWithData:imgData];
});
});
return cell;
}
... ...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (cell == nil) {
cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
cell.poster.image = [UIImage imageWithData:data];
// do whatever you want with image
}
}];
return cell;
}
解決方法は?
手っ取り早く戦術的な修正をしたいのであれば、セル画像が初期化されていることと、セルの行がまだ表示されていることを確認する必要があります、例.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
cell.poster.image = nil; // or cell.poster.image = [UIImage imageNamed:@"placeholder.png"];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg", self.myJson[indexPath.row][@"movieId"]]];
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (data) {
UIImage *image = [UIImage imageWithData:data];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
MyCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
if (updateCell)
updateCell.poster.image = image;
});
}
}
}];
[task resume];
return cell;
}
上記のコードでは、セルが再利用されることに起因するいくつかの問題に対処しています。
-
バックグランドリクエストを開始する前にセル画像を初期化していない(つまり、新しい画像をダウンロードしている間、キューから外されたセルの最後の画像がまだ表示されている)。必ず
nil
そのimage
プロパティを使用しないと、画像のちらつきが発生します。 -
もっと微妙な問題は、本当に遅いネットワークでは、セルが画面からスクロールする前に非同期リクエストが終了しないかもしれないことです。この場合
UITableView
メソッドcellForRowAtIndexPath:
(という名前がついています(似たような名前のUITableViewDataSource
メソッドtableView:cellForRowAtIndexPath:
) を使用して、その行のセルがまだ表示されているかどうかを確認します。このメソッドはnil
セルが表示されていない場合問題は、非同期メソッドが完了するまでにセルがスクロールしてしまい、さらに悪いことに、そのセルがテーブルの別の行に再利用されてしまっていることです。行がまだ表示されているかどうかを確認することで、誤って画面外にスクロールした行の画像で画像を更新しないようにします。
-
この質問とは少し関係ありませんが、私は特に、現代の慣習とAPIを活用するためにこれを更新する必要に迫られました。
-
使用方法
NSURLSession
をディスパッチするのではなく-[NSData contentsOfURL:]
をバックグラウンドのキューにコピーします。 -
使用方法
dequeueReusableCellWithIdentifier:forIndexPath:
よりもdequeueReusableCellWithIdentifier:
(ただし、その識別子には必ずセルプロトタイプかレジスタークラスかNIBを使用すること); そして -
に準拠したクラス名にしてみました。 Cocoaの命名規則 (つまり、大文字で始まる)。
-
これらの修正を行っても、問題はあります。
-
上記のコードでは、ダウンロードした画像をキャッシュしていません。つまり、画像を画面外にスクロールして画面内に戻した場合、アプリは再び画像を取得しようとするかもしれません。幸運なことに、サーバーのレスポンス・ヘッダが
NSURLSession
とNSURLCache
しかし、そうでない場合は、不必要なサーバーリクエストを行い、より遅いUXを提供することになります。 -
画面外にスクロールするセルのリクエストはキャンセルしていません。したがって、100 行目まで急速にスクロールした場合、その行の画像は、もう表示されていない前の 99 行のリクエストの後ろに滞留している可能性があります。最高のUXを実現するためには、常に可視セルのリクエストを優先するようにします。
これらの問題を解決する最も簡単な方法は
UIImageView
で提供されているようなカテゴリです。
SDWebImage
または
AFNetworking
. 必要であれば、自分でコードを書いて上記の問題に対処することもできますが、大変な作業ですし、上記のような
UIImageView
のカテゴリはすでにあなたのためにこれをやってくれています。
関連
-
XCode のコンパイル例外を解決する clang: error: linker command failed with exit code 1
-
JenkinsがIOSを自動パッケージングしてモミを配布
-
[解決済み] Unwind segueは何に使うのか、どう使うのか?
-
[解決済み] UINavigationBarの1px下の行を非表示にする方法
-
[解決済み] SwiftでURLから画像を読み込む/ダウンロードする
-
[解決済み] UILabelで複数行のテキストを表示する
-
[解決済み] iOSで現在のデバイスの言語を取得するには?
-
[解決済み] iPadマルチタスクのサポートには、これらの方向が必要です。
-
[解決済み] NSNotificationCenterのaddObserver in Swift
-
[解決済み] ファイルはユニバーサル(3スライス)ですが、iOSの静的ライブラリのための(n)ARMv7-sスライスエラーが含まれていない、どうにかして回避するには?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] UITableViewの選択を無効にするにはどうすればよいですか?
-
[解決済み] 制約条件の変更をアニメーションで表現するには?
-
[解決済み] iOSのバージョンを確認する方法を教えてください。
-
[解決済み] Objective-Cでデリゲートを作成するにはどうしたらいいですか?
-
[解決済み] Xcode 12、iOS Simulator用にビルドしても、iOS用にビルドされたオブジェクトファイルでは、アーキテクチャ「arm64」用にリンクされます。
-
[解決済み] iPhone UITextField - プレースホルダーの文字色を変更する
-
[解決済み] IBOutletsはARCのもとで強くなるべきか、弱くなるべきか?
-
[解決済み] SwiftでUIAlertViewを作成するにはどうしたらいいですか?
-
[解決済み] テキストフィールドを移動する方法(次へボタン/完了ボタン)
-
[解決済み] iPadマルチタスクのサポートには、これらの方向が必要です。