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;
}
ベストアンサー1
迅速な戦術的な修正を探していると仮定すると、セル イメージが初期化されていること、およびセルの行がまだ表示されていることを確認する必要があります。例:
- (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
次のようなカテゴリを使用することです。SDWebイメージまたはAFネットワーキング必要に応じて、上記の問題に対処するための独自のコードを記述することもできますが、これは非常に手間のかかる作業であり、上記のUIImageView
カテゴリではすでにこの処理が行われています。