Iphone UITableView, Cells, and notifications - cells not displaying correct image after notification
So I'm stumped on this subject and not sure what the best way to do this is. I need to download from an api, store in core data, then display a set of cells of a tableview with just a title, description and picture (the picture is a url, so I need to download that too). The tableview only has one section
I have set up a structure where in my TableViewController I'm making self.tableview a listener to an NSNotification (specified during init). I have created a custom cell view, with an extra property called firstLoad that gets set to YES during initialization. In my tableView cellForRowAtIndexPath, I'm checking for the firstload property, and if it's set to yes, I'm calling a function to download the image from the url. When that happens, a notification is sent out, caught by a function in the tableviewcontroller, I get the indexpathsforvisiblerows and if the row from the notification matches the row from one of the indexpathsforvisiblerows array, i set the cell's image and then reloadData
What i'm having troubles with is that when I start scrolling the tableview, it looks like sometimes the images get set for the wrong cell. The cells are also being reused.
I'm not sure how to handle this exactly. Thanks so much for any help!!
This is the code for cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"APCell";
APTableViewGenCell *cell = (APTableViewGenCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[APTableViewGenCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier useType:_useType] autorelease];
}
// Configure the cell...
if (APUseTypeGroupsWithEvents == _useType) {
APGroup *group = [_fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = group.name;
UIImage *logoImage = [UIImage imageNamed:@"groups.png"];
if (group.logo.imageURL && (cell.firstLoad == (BOOL *)YES)){
cell.imageView.image = [logoImage scaleToSize:CGSizeMake(kTableImageWidth, kTableImageHeight)];
开发者_如何学C APGroundControl *gc = [APGroundControl sharedGroundControl];
[gc retrieveImageWithURL:group.logo.imageURL indexPath:indexPath withDefaultImgName:@"groups" andDefaultImgType:@"png" sender:self];
}
cell.detailTextLabel.text = group.groupDescription;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
return cell;
}
And this is the notification received handler:
- (void)imageRetrieved:(NSNotification *)notification {
NSLog(@"%@", @"NOTIFICATION RECEIVED");
if ([[[notification userInfo] objectForKey:kNewImageDownloadedSenderKey] isEqual:self]) {
// If the cell is still visible show the image.
NSIndexPath *indexPath = [[notification userInfo] objectForKey:kNewImageDownloadedIndexPathKey];
if (indexPath) {
NSArray *indexPaths = [self.tableView indexPathsForVisibleRows];
for (NSIndexPath *path in indexPaths) {
if (indexPath.row == path.row) {
APTableViewGenCell *cell = (APTableViewGenCell *)[self.tableView cellForRowAtIndexPath:path];
NSData *imageData = [[notification userInfo] objectForKey:kNewImageDownloadedDataKey];
UIImage *logoImage = nil;
cell.firstLoad = (BOOL *)NO;
if ([imageData isKindOfClass:[UIImage class]]) {
logoImage = (UIImage *)imageData;
logoImage = [logoImage scaleToSize:CGSizeMake(kTableImageWidth, kTableImageHeight)];
cell.imageView.image = logoImage; // @todo: get rid of this temp check once all image gets moved over to the new retrieve function
}
else {
logoImage = [UIImage imageWithData:imageData];
logoImage = [logoImage scaleToSize:CGSizeMake(kTableImageWidth, kTableImageHeight)];
cell.imageView.image = logoImage;//[UIImage imageWithData:imageData];
}
break;
}
}
[self.tableView reloadData];
}
}
}
And the code for retrieveImageWithUrl:
- (void)retrieveImageWithURL:(NSString *)url indexPath:(NSIndexPath *)indexPathOrNil withDefaultImgName:(NSString *)defaultImgName andDefaultImgType:(NSString *) defaultImgType sender:(id)sender {
NSLog(@"%@", @"RETRIEVE IMAGE CALLED");
NSLog(@"%@", indexPathOrNil);
[self queueBlock:^{
NSData *imageData = nil;
// Get image locally
if ([url isEqualToString:kGetGroupDefaultLogoURL]){
NSString *filePath = [[NSBundle mainBundle] pathForResource:defaultImgName ofType:defaultImgType];
imageData = [NSData dataWithContentsOfFile:filePath];
}
// @todo: add a default user logo
else if (url == nil) {
NSLog(@"%@", @"Default Retrieve");
NSString *filePath = [[NSBundle mainBundle] pathForResource:defaultImgName ofType:defaultImgType];
imageData = [NSData dataWithContentsOfFile:filePath];
}
else {
NSLog(@"%@", @"Local Retrieve");
imageData = [_imageCache objectForKey:url];
}
// If local image does not exist get it from the internet
if (imageData == nil) {
NSLog(@"%@", @"Remote Retrieve");
NSLog(@"URL = %@", url);
imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
if (imageData == nil) {
NSString *filePath = [[NSBundle mainBundle] pathForResource:defaultImgName ofType:defaultImgType];
imageData = [NSData dataWithContentsOfFile:filePath];
}
[_imageCache setObject:imageData forKey:url];
}
// Send out notification
NSMutableArray *objects = [NSMutableArray array];
NSMutableArray *keys = [NSMutableArray array];
if (indexPathOrNil) {
[objects addObject:indexPathOrNil];
[keys addObject:kNewImageDownloadedIndexPathKey];
}
[objects addObject:sender];
[keys addObject:kNewImageDownloadedSenderKey];
[objects addObject:imageData];
[keys addObject:kNewImageDownloadedDataKey];
NSDictionary *userInfo = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@", @"NOTIFICATION SENT");
[[NSNotificationCenter defaultCenter] postNotificationName:kNewImageDownloadedNotification object:self userInfo:userInfo];
});
}];
}
Ok, first of all, the firstLoad
-flag that you are using doesn't make sense. You need to know whether your image was already loaded but since reusable cells can't be connected with a specific image, this is not the way to do that.
You updated your post a few times and by now, it isn't exactly clear what your current problem is. So I'm just going to lay out the way for a proper and clean solution for you:
First of all, you need a place to store your images. A NSMutableDictionary
will serve just fine. Use the keys to store your URLs and the value to store the images.
When you enter a new cell, look if the URL is already in the dictionary. If so, just fill it in. Otherwise, start the loading process. If you do so, you should also create a new entry in the dictionary, which stores the URL and nil
for the image. This helps you to avoid loading an image with is not yet ready twice. In your 'Loading complete' method, you replace the nil
value with your image.
And this should be it. Naturally, you will need to tailor these steps according to your specific needs, but this shouldn't be much of a problem. Hope this helps!
PS: Also, you should review your code to find redundant or overcomplicated calls. Without knowing you specific setup, iterating over an NSIndexPath
looks pretty odd, you should be able to access your values directly.
It might be that the cells which are getting dequeued from the table view keep the image that they had in their previous life. Try setting the imageview image to nil in cellForRowAtIndexPath (before updating it to its new image which could take some time).
精彩评论