如何让 UITableViewCell 图像更新为下载的图像,而无需滚动 UITableView
我正在尝试使用我自己的常用 UITableView + 异步下载 + 缓存技术。我正在做的是,对于在 cellForRowAtIndexPath: 中出列的每个单元格
1-Check if it's corresponding thumbnail image is already 'cached' in /Library/Caches
2-If it is, just use that.
3-If not, load a default image and enqueue an NSInvocationOperation to take care of it:
4a-The NSInvocationOperation gets the image from a remote server
4b-Does the UIGraphicsBeginContext thing to scale down the image to 40x40
4c-saves the scaled down version to /Library/Cache
4d-'SHOULD' update the cell's image to the new downloaded and downsized image, if the cell is still visible.
,我无法弄清楚如何让单元格更新其图像,除非我手动将它们滚动到屏幕上并返回。我能够实现的唯一技巧是让 NSOperation 在完成后通过 PerformSelectorOnMainThread 调用主线程,然后主线程可以调用 [viewtable reloadData]。但这似乎很浪费:每次单元格的新图像准备好时,我都会重新加载整个表格。
作为一种不那么浪费的方法,我让主线程设置一个 bool 标志,然后,当scrollViewDidEndDecelerating 时,如果设置了该标志,则会调用 [viewtable reloadData]。通过这种方法,单元格仅在用户完成滚动时刷新。
但是,我仍然希望仅更新可见单元格,如果它们的缓存图像在它们仍然可见时已准备好(意味着用户没有将它们滚动到视图之外)。
到目前为止,这是我的代码:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleSubtitle
reuseIdentifier: CellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.selectionStyle = UITableViewCellSelectionStyleGray;
}
// Configure the cell...
cell.textLabel.text = [[dbData objectAtIndex:indexPath.row] objectAtIndex:0];
cell.detailTextLabel.text = [[dbData objectAtIndex:indexPath.row] objectAtIndex:1];
NSString *ImageName = [[dbData objectAtIndex:indexPath.row] objectAtIndex:2];
NSString *cachedImageName = [[[ImageName stringByDeletingPathExtension] stringByAppendingString:thumbnailSizeSuffix] stringByAppendingPathExtension:@"png"];
NSString *cachedImagePath = [cachePath stringByAppendingPathComponent:cachedImageName];
if([[NSFileManager defaultManager] fileExistsAtPath:cachedImagePath])
cell.imageView.image = [UIImage imageWithContentsOfFile:cachedImagePath];
else
{
cell.imageView.image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:defaultNeedsDownloadIconFile ofType:@"png"]];
NSArray *package = [NSArray arrayWithObjects:ImageName, cachedImagePath ,referencingTable, nil];
NSInvocationOperation *concurrentImageLoader = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadURI:) object:package];
[concurrentQueue addOperation: concurrentImageLoader];
[concurrentImageLoader release];
}
return cell;
}
对于 NSInvocableOperation 的“内核”,我尝试过以下操作:
- (void)loadURI:(id)package
{
NSArray *payload = (NSArray*)package;
NSString *imageName = [payload objectAtIndex:0];
NSString *cachedImagePath = [payload objectAtIndex:1];
NSString *imageURL = [NSString stringWithFormat:@"http://www.useanddisposeof.com/VentanaSurDB/%@/photo/%@",[payload objectAtIndex:2], imageName];
UIImage *newThumbnail = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
if(!newThumbnail)
newThumbnail = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:defaultNotFoundIconFile ofType:@"png"]];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, thumbnailSize.width, thumbnailSize.height)];
imageView.layer.borderColor = [UIColor blackColor].CGColor;
imageView.layer.cornerRadius = 4.0;
imageView.layer.masksToBounds = YES;
imageView.layer.borderWidth = 1.0;
imageView.image = newThumbnail;
UIGraphicsBeginImageContext(CGSizeMake(thumbnailSize.width, thumbnailSize.height));
[imageView.layer renderInContext:UIGraphicsGetCurrentContext()];
newThumbnail = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[imageView release];
[UIImagePNGRepresentation(newThumbnail) writeToFile:cachedImagePath atomically:YES];
[self performSelectorOnMainThread:@selector(updateCellImage) withObject:nil waitUntilDone:NO];
}
这是主线程中用于刷新表视图的代码:
- (void)updateCellImage:(id)package
{
needReloadCachedImages = YES;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
// I know, I know, there's a race condition here.. I'll fix it if this code stays.
if(needReloadCachedImages)
[self.tableView reloadData];
needReloadCachedImages = NO;
}
有什么想法吗?
I'm trying to pull my own flavor of the usual UITableView + async download + cache technique. What I'm doing is, for each cell that gets dequeued in cellForRowAtIndexPath:
1-Check if it's corresponding thumbnail image is already 'cached' in /Library/Caches
2-If it is, just use that.
3-If not, load a default image and enqueue an NSInvocationOperation to take care of it:
4a-The NSInvocationOperation gets the image from a remote server
4b-Does the UIGraphicsBeginContext thing to scale down the image to 40x40
4c-saves the scaled down version to /Library/Cache
4d-'SHOULD' update the cell's image to the new downloaded and downsized image, if the cell is still visible.
However, I can't figure out how to get the cells to update their images unless I manually scroll them off and back on screen. The only hack I've been able to pull is having the NSOperation call the main thread when done, through performSelectorOnMainThread and the main thread can then call [viewtable reloadData]. But this seems wasteful: I'm reloading the whole table each time a cell's new image is ready.
As a less wasteful approach, I have the main thread instead set a bool flag and then, when scrollViewDidEndDecelerating, if the flag was set, a call to [viewtable reloadData] is made. With this approach the cells only refresh when the user is done scrolling.
But still, I'd like for just the visible cells to update, if their cached images are ready while they are still visible (meaning the user didn't scroll them off the view).
Here's my code so far:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleSubtitle
reuseIdentifier: CellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.selectionStyle = UITableViewCellSelectionStyleGray;
}
// Configure the cell...
cell.textLabel.text = [[dbData objectAtIndex:indexPath.row] objectAtIndex:0];
cell.detailTextLabel.text = [[dbData objectAtIndex:indexPath.row] objectAtIndex:1];
NSString *ImageName = [[dbData objectAtIndex:indexPath.row] objectAtIndex:2];
NSString *cachedImageName = [[[ImageName stringByDeletingPathExtension] stringByAppendingString:thumbnailSizeSuffix] stringByAppendingPathExtension:@"png"];
NSString *cachedImagePath = [cachePath stringByAppendingPathComponent:cachedImageName];
if([[NSFileManager defaultManager] fileExistsAtPath:cachedImagePath])
cell.imageView.image = [UIImage imageWithContentsOfFile:cachedImagePath];
else
{
cell.imageView.image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:defaultNeedsDownloadIconFile ofType:@"png"]];
NSArray *package = [NSArray arrayWithObjects:ImageName, cachedImagePath ,referencingTable, nil];
NSInvocationOperation *concurrentImageLoader = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadURI:) object:package];
[concurrentQueue addOperation: concurrentImageLoader];
[concurrentImageLoader release];
}
return cell;
}
For the "kernel" of the NSInvocationOperation, I've tried this:
- (void)loadURI:(id)package
{
NSArray *payload = (NSArray*)package;
NSString *imageName = [payload objectAtIndex:0];
NSString *cachedImagePath = [payload objectAtIndex:1];
NSString *imageURL = [NSString stringWithFormat:@"http://www.useanddisposeof.com/VentanaSurDB/%@/photo/%@",[payload objectAtIndex:2], imageName];
UIImage *newThumbnail = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
if(!newThumbnail)
newThumbnail = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:defaultNotFoundIconFile ofType:@"png"]];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, thumbnailSize.width, thumbnailSize.height)];
imageView.layer.borderColor = [UIColor blackColor].CGColor;
imageView.layer.cornerRadius = 4.0;
imageView.layer.masksToBounds = YES;
imageView.layer.borderWidth = 1.0;
imageView.image = newThumbnail;
UIGraphicsBeginImageContext(CGSizeMake(thumbnailSize.width, thumbnailSize.height));
[imageView.layer renderInContext:UIGraphicsGetCurrentContext()];
newThumbnail = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[imageView release];
[UIImagePNGRepresentation(newThumbnail) writeToFile:cachedImagePath atomically:YES];
[self performSelectorOnMainThread:@selector(updateCellImage) withObject:nil waitUntilDone:NO];
}
And this is the code back in the main thread for refreshing the tableview:
- (void)updateCellImage:(id)package
{
needReloadCachedImages = YES;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
// I know, I know, there's a race condition here.. I'll fix it if this code stays.
if(needReloadCachedImages)
[self.tableView reloadData];
needReloadCachedImages = NO;
}
Any ideas?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
reloadData
仅重新加载可见单元格,而不是整个表格,这就是您所说的您想要的。reloadData
only reloads visible cells, not the whole table, which is what you say you want.给一些开源一个尝试?对于一个更简单的问题来说,这太过费力了。还有关于此的一个很好的教程,它可能会给你一个想法你可能做错了什么。
How about giving some open-source a try? This is too much effort for a simpler problem. There is also a nice tutorial on this, which might give you an idea on what you might be doing wrong.