问题
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?
回答1:
But this seems wasteful: I'm reloading the whole table each time a cell's new image is ready.
reloadData
only reloads visible cells, not the whole table, which is what you say you want.
回答2:
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.
来源:https://stackoverflow.com/questions/7874505/how-to-get-uitableviewcell-images-to-update-to-downloaded-images-without-having