在 cellForRowAtIndexPath 中使用 dispatch_async 加载图像
Loading a image using dispatch_async within cellForRowAtIndexPath
我正在尝试从 url 加载图像。
以下是我的代码..
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = nil;
cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:@""];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:nil];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
dispatch_async (dispatch_get_main_queue(), ^{
NSData * storeImageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:productImageArray[indexPath.row]]];
self.productImage.image = [UIImage imageWithData:storeImageData];
});
[cell.contentView addSubview:self.productImage];
return cell;
}
问题是,
- UI 冻结,直到图像加载完毕。
- 只有最后一个单元格正在加载图像,其余单元格未加载图像。
我该如何解决这个问题?
尝试 NSURLSessionTask
:
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(), ^{
UITableViewCell * cell = (id)[tableView cellForRowAtIndexPath:indexPath];
if (cell)
cell. productImage.image = image;
});
}
}
}];
[task resume];
您正在主 UI 线程上同步 下载图像,这导致屏幕冻结。
请按照以下文章解决问题:
iOS: How To Download Images Asynchronously (And Make Your UITableView Scroll Fast)
和也回答你的第二点:
您似乎使用了在 cellForRowAtIndexPath 方法之外实例化的单个 imageView,然后您试图将其添加到每个单元格中,这将导致图像从先前的单元格中删除,并添加到当前单元格中
因为一个视图只能有一个父视图,一旦您尝试在其他视图中添加子视图,它就会从当前的父视图中删除
您可以使用 GCD 在后台线程中加载图像,如下所示:
//get a dispatch queue
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this will start the image loading in bg
dispatch_async(concurrentQueue, ^{
NSData *image = [[NSData alloc] initWithContentsOfURL:imageURL];
//this will set the image when loading is finished
dispatch_async(dispatch_get_main_queue(), ^{
imageView.image = [UIImage imageWithData:image];
});
});
但是解决这些问题的最简单方法是使用 UIImageView 类别,例如 SDWebImage or AFNetworking 提供的类别。如果你愿意,你可以自己写代码来处理上面的问题,但是工作量很大,上面的UIImageView类已经帮你搞定了。
试试这个:
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.imageView.image = image;
或者有时我所做的是更新模型,然后在特定的 indexPath 处重新加载单元格:
myModel.image = downloadedThumbImage;
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationNone];
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"top50places";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//getting the selected row image
NSDictionary* currentImageDictionary=[self.topfifty objectAtIndex:indexPath.row];//topFifty is an array of image dictionaries
UIImage *currentImage = [currentImageDictionary objectForKey:@"image"];
if (currentImage) {
// we already fetched the image, so we just set it to the cell's image view
cell.imageView.image = currentImage;
}
else {
// we don't have the image, so we need to fetch it from the server
// In the meantime, we can set some place holder image
UIImage *palceholderImage = [UIImage imageNamed:@"placeholder.png"];
cell.imageView.image = palceholderImage;
// set the placeholder as the current image to your model, so you won't
// download the image multiple times (can happen if you reload this cell while
// download is in progress)
[currentImageDictionary setObject:palceholderImage forKey:@"image"];
// then download the image
// creating the download queue
dispatch_queue_t downloadQueue=dispatch_queue_create("thumbnailImage", NULL);
dispatch_async(downloadQueue, ^{
UIImage *downloadedThumbImage=[self getImage:currentImageDictionary] ;
//Need to go back to the main thread since this is UI related
dispatch_async(dispatch_get_main_queue(), ^{
// store the downloaded image in your model
[currentImageDictionary setObject:image forKey:@"image"];
// update UI
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.imageView.image = image;
});
});
dispatch_release(downloadQueue);
}
return cell;
}
我觉得这是异步下载图像的最佳方法。我将始终使用以下方法完成此任务。
在cellForRowAtIndexPath:
NSURL *imgURL = [[NSURL URLWithString:productImageArray[indexPath.row]];
[self downloadImageWithURL:imgURL
completionBlock:^(BOOL succeeded, UIImage *image) {
self.productImage.image=image;
}];
并添加此方法
- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error )
{
data = [NSData dataWithContentsOfURL:url];
UIImage *image = [[UIImage alloc] initWithData:data];
completionBlock(YES,image);
} else{
NSLog(@"Error in downloading image:%@",url);
completionBlock(NO,nil);
}
}];
}
使用这个,即使我们在下载图片时有任何问题也能更好地知道。
@MGR,你可以使用 Dispatch async,但是有一个库 https://www.cocoacontrols.com/controls/hanekeswift,一个很棒的库。它将处理下载图像,并且有一种方法可以在 imageview.public 中加载图像
func hnk_setImageFromURL(URL: NSURL, placeholder: UIImage? = default, format: Haneke.Format<UIImage>? = default, failure fail: ((NSError?) -> ())? = default, success succeed: ((UIImage) -> ())? = default)
它还有缓存功能。所以你可以简单地在 cellForRow
中实现这个功能。那 it.evertything 将由 Haneke 处理。
* 请使用此框架 *
https://github.com/onevcat/Kingfisher
这带来了比 Hanekeswift
更好的性能和自定义
我正在尝试从 url 加载图像。
以下是我的代码..
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = nil;
cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:@""];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:nil];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
dispatch_async (dispatch_get_main_queue(), ^{
NSData * storeImageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:productImageArray[indexPath.row]]];
self.productImage.image = [UIImage imageWithData:storeImageData];
});
[cell.contentView addSubview:self.productImage];
return cell;
}
问题是,
- UI 冻结,直到图像加载完毕。
- 只有最后一个单元格正在加载图像,其余单元格未加载图像。
我该如何解决这个问题?
尝试 NSURLSessionTask
:
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(), ^{
UITableViewCell * cell = (id)[tableView cellForRowAtIndexPath:indexPath];
if (cell)
cell. productImage.image = image;
});
}
}
}];
[task resume];
您正在主 UI 线程上同步 下载图像,这导致屏幕冻结。
请按照以下文章解决问题:
iOS: How To Download Images Asynchronously (And Make Your UITableView Scroll Fast)
和也回答你的第二点:
您似乎使用了在 cellForRowAtIndexPath 方法之外实例化的单个 imageView,然后您试图将其添加到每个单元格中,这将导致图像从先前的单元格中删除,并添加到当前单元格中
因为一个视图只能有一个父视图,一旦您尝试在其他视图中添加子视图,它就会从当前的父视图中删除
您可以使用 GCD 在后台线程中加载图像,如下所示:
//get a dispatch queue
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this will start the image loading in bg
dispatch_async(concurrentQueue, ^{
NSData *image = [[NSData alloc] initWithContentsOfURL:imageURL];
//this will set the image when loading is finished
dispatch_async(dispatch_get_main_queue(), ^{
imageView.image = [UIImage imageWithData:image];
});
});
但是解决这些问题的最简单方法是使用 UIImageView 类别,例如 SDWebImage or AFNetworking 提供的类别。如果你愿意,你可以自己写代码来处理上面的问题,但是工作量很大,上面的UIImageView类已经帮你搞定了。
试试这个:
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.imageView.image = image;
或者有时我所做的是更新模型,然后在特定的 indexPath 处重新加载单元格:
myModel.image = downloadedThumbImage;
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationNone];
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"top50places";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//getting the selected row image
NSDictionary* currentImageDictionary=[self.topfifty objectAtIndex:indexPath.row];//topFifty is an array of image dictionaries
UIImage *currentImage = [currentImageDictionary objectForKey:@"image"];
if (currentImage) {
// we already fetched the image, so we just set it to the cell's image view
cell.imageView.image = currentImage;
}
else {
// we don't have the image, so we need to fetch it from the server
// In the meantime, we can set some place holder image
UIImage *palceholderImage = [UIImage imageNamed:@"placeholder.png"];
cell.imageView.image = palceholderImage;
// set the placeholder as the current image to your model, so you won't
// download the image multiple times (can happen if you reload this cell while
// download is in progress)
[currentImageDictionary setObject:palceholderImage forKey:@"image"];
// then download the image
// creating the download queue
dispatch_queue_t downloadQueue=dispatch_queue_create("thumbnailImage", NULL);
dispatch_async(downloadQueue, ^{
UIImage *downloadedThumbImage=[self getImage:currentImageDictionary] ;
//Need to go back to the main thread since this is UI related
dispatch_async(dispatch_get_main_queue(), ^{
// store the downloaded image in your model
[currentImageDictionary setObject:image forKey:@"image"];
// update UI
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.imageView.image = image;
});
});
dispatch_release(downloadQueue);
}
return cell;
}
我觉得这是异步下载图像的最佳方法。我将始终使用以下方法完成此任务。
在cellForRowAtIndexPath:
NSURL *imgURL = [[NSURL URLWithString:productImageArray[indexPath.row]];
[self downloadImageWithURL:imgURL
completionBlock:^(BOOL succeeded, UIImage *image) {
self.productImage.image=image;
}];
并添加此方法
- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error )
{
data = [NSData dataWithContentsOfURL:url];
UIImage *image = [[UIImage alloc] initWithData:data];
completionBlock(YES,image);
} else{
NSLog(@"Error in downloading image:%@",url);
completionBlock(NO,nil);
}
}];
}
使用这个,即使我们在下载图片时有任何问题也能更好地知道。
@MGR,你可以使用 Dispatch async,但是有一个库 https://www.cocoacontrols.com/controls/hanekeswift,一个很棒的库。它将处理下载图像,并且有一种方法可以在 imageview.public 中加载图像
func hnk_setImageFromURL(URL: NSURL, placeholder: UIImage? = default, format: Haneke.Format<UIImage>? = default, failure fail: ((NSError?) -> ())? = default, success succeed: ((UIImage) -> ())? = default)
它还有缓存功能。所以你可以简单地在 cellForRow
中实现这个功能。那 it.evertything 将由 Haneke 处理。
* 请使用此框架 * https://github.com/onevcat/Kingfisher
这带来了比 Hanekeswift