長いタイトルですが。。
UITableViewCellに画像を表示する時に、画像の読み込みに時間がかかるので、GCDで非同期処理をする時の話です。
画像を表示するのがcellなので、次のようなコードを書いていたのですが、これは間違いでした。
このコードでは、UITableViewが高速にスクロールされた時などに、画像の読み込みが終わる前に画面上からいなくなり、Re-Useされることがあります。そうなってしまうと、Re-Useされる前に表示するはずだった画像がRe-Use後に表示する画像の前に一瞬だけ表示されてしまい、見た目がちょっとかっこ悪くなります。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NSString *path = self.pathArray[indexPath.row];
dispatch_queue_t q_global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(q_global, ^{
// 重い処理
UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
dispatch_queue_t q_main = dispatch_get_main_queue();
dispatch_async(q_main, ^{
// cellをキャプチャ
// -> 重い処理の後には、別のindexPathのcellかもしれない
cell.imageView.image = image;
});
});
}
何か良い方法はないものかと調べていたら、このブログ記事を見つけました。→UITableViewCellで画像を非同期でロードしようとしたときに困った事②
この記事を読んでいて、cellではなくindexPathをキャプチャすれば解決することに気づきました。
ソースコードを書き直すと、次のようになります。このコードであれば、目的のindexPathのcellに画像を表示することができます。
なお、cellForRowAtIndexPath:メソッドは、indexPathが示すcellが画面上に表示されていなければnilを返します。
[2013/7/21 0:35 追記]
UITableView#cellForRowAtIndexPath:だと、画面上に表示されていてもnilが返される事があるとのコメントを頂きました。
画面上のcellにもれなく画像を表示できるよう、indexPathだけでなくcellもキャプチャするようソースを修正しました。
また、cellに現在のindexPathを持たせるために、カテゴリを使用してUITableViewCellクラスを拡張しています。
#import <UIKit/UIKit.h>
// UITableViewCellクラスを拡張し、currentIndexPathプロパティを追加
@interface UITableViewCell (CurrentIndexPath)
@property (nonatomic, strong) NSIndexPath *currentIndexPath;
@end
#import "UITableViewCell+CurrentIndexPath.h"
#import <objc/runtime.h>
static char kCurrentIndexPathKey;
// currentIndexPathプロパティの実装
@implementation UITableViewCell (CurrentIndexPath)
// getter
- (NSIndexPath *)currentIndexPath
{
return objc_getAssociatedObject(self, &kCurrentIndexPathKey);
}
// setter
- (void)setCurrentIndexPath:(NSIndexPath *)currentIndexPath
{
objc_setAssociatedObject(self, &kCurrentIndexPathKey, currentIndexPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
// #import "UITableViewCell+CurrentIndexPath.h"
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// cellをデキューしたら、現在のindexPathを持たせる
cell.currentIndexPath = indexPath
NSString *path = self.pathArray[indexPath.row];
dispatch_queue_t q_global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(q_global, ^{
// 重い処理
UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
dispatch_queue_t q_main = dispatch_get_main_queue();
dispatch_async(q_main, ^{
// cellとindexPathをキャプチャ
// -> cellが持っているindexPathとキャプチャしたindexPathが等しい場合のみ画像をセット
if ([cell.currentIndexPath isEqual:indexPath]) {
cell.imageView.image = image;
}
});
});
}