[英]GCD UITableView asynchronous load images, wrong cells are loaded until new image download
[英]Asynchronous downloading of images for UITableView with GCD
我正在使用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;
}
這里的問題是您的圖像獲取塊正在保存對tableview單元的引用。 下載完成后,即使您回收了單元格以顯示其他行,它也會設置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;
}
關鍵是您沒有完全了解單元重用概念。 這與異步下載不太吻合。
塊
^{
cell.imageView.image = image;
[cell setNeedsLayout];
}
在請求完成並且所有數據都已加載時被執行。 但是,在創建塊時,單元會獲得其值。
到執行塊時,單元仍指向現有單元之一。 但是用戶很有可能繼續滾動。 同時,該單元格對象已被重新使用,並且該圖像與“ 舊 ”單元格相關聯,該單元格已被重用,分配和顯示。 之后不久,除非用戶進一步滾動,否則將加載並分配並顯示正確的圖像。 等等等等。
您應該尋找一種更聰明的方法。 有很多花絮。 Google用於延遲加載圖片。
使用索引路徑獲取單元格。 如果不可見,則該單元格將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;
}
我一直在研究此問題,並且通過自定義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聲明兩個屬性,然后在ViewDidLoad中對其進行初始化:
@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];
}
.
.
.
讓我們假設您的數據源是NSMutableDictionary的數組(或NSManagedObjectContext)。 您可以通過緩存輕松地為每個單元下載圖像,如下所示:
.
.
.
- (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;
}
希望對某些開發人員有所幫助! 干杯。
評分: iOS 7中的表格視圖圖像
您是否考慮過使用SDWebImage? 它也從給定的URL異步下載圖像。 我沒有使用整個庫(僅導入了UIImageView + WebCache.h)。 導入后,您所需要做的就是這樣調用方法:
[UIImageXYZ sd_setImageWithURL:["url you're retrieving from"] placeholderImage:[UIImage imageNamed:@"defaultProfile.jpeg"]];
如果您使用的是AFNetworking 2.0,可能會顯得有些矯kill過正,但這對我來說算是可行的。
不要重新發明輪子,只需使用這對很棒的豆莢:
https://github.com/rs/SDWebImage
https://github.com/JJSaccolo/UIActivityIndicator-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];
除了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)
}
});
}
對於那些擔心索引路徑更改且不一致的人,一個潛在的解決方案是將UITableViewCell或UICollectionViewCell子類化,並添加一個名為stringTag之類的實例變量。 然后,將要下載的照片的URL放在stringTag中。 設置圖像時,請檢查stringTag是否仍然是正確的URL。
有關更多詳細信息,請參見此答案: 異步獲取已完成:仍在顯示單元嗎? 。
這是我的課程:
import Foundation
import UIKit
class ImageAsyncCollectionViewCell : UICollectionViewCell {
var stringTag: String?
}
然后在使用單元格時:
cell.stringTag = photoKey
cell.imageView.image = self.blankImage
if ImageCache.default.imageCachedType(forKey: photoKey).cached {
ImageCache.default.retrieveImage(forKey: photoKey, options: nil) {
image, cacheType in
if let image = image {
DispatchQueue.main.async {
if cell.stringTag == photoKey {
cell.imageView.image = image
}
}
} else {
print("Doesn't exist in cache, but should")
self.setCellWithPhoto(photoKey: photoKey, cell: cell)
}
}
} else {
self.setCellWithPhoto(photoKey: photoKey, cell: cell)
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.