Assertion failure in -[UICollectionView _endItemAnimations] - objective-c

I'm quite a newbie in objective-c programming. I'm trying to make my first app and I can't solve one issue.
I use UICollectionView with NSFetchedResultController in my app. The app downloads data from the web and uses NSOperationQueue to do it asynchronously. Everything works fine when I am testing my app in a simulator but when I'm testing it on iPad 3 quite often I'm getting this error.
* Assertion failure in -[UICollectionView _endItemAnimations], /SourceCache/UIKit/UIKit-2380.17/UICollectionView.m:2801
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (28) must be equal to the number of items contained in that section before the update (26), plus or minus the number of items inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'
I do not know why because I set maxConcurrentOperationCount to 1 and I thought that in this case all operations resulting in inserting items to UICollectionView in order… What could be wrong and what should I change?
mateusz
If anyone is still interested that's my code:
I'm calling this method when my NSOperation is finished:
- (void)collectionViewUpdaterDidFinish:(CollectionViewUpdater *)updater {
[Artwork artworkWithBehanceInfo:self.projectsData
andImageData:updater.imageData
atIndex:updater.projectIndex
forIndexInDataBase:updater.indexPathInCollectionView.item withDataHelper:self.dataHelper];
NSIndexPath *indexPath = updater.indexPathInCollectionView;
[self.dataHelper fetchData];
[self.myCollectionView insertItemsAtIndexPaths:#[indexPath]];
[self.pendingOperations.downloadsInProgress removeObjectForKey:indexPath];
if ([self.dataHelper.fetchedResultsController.fetchedObjects count]==self.numberOfLastItemOnPage){
[self.refresh endRefreshing];
[self stopAnimation];
self.progressView.hidden = YES;
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ResultCell";
ResultCellView *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
Artwork *artwork = [self.dataHelper.fetchedResultsController objectAtIndexPath:indexPath];
cell.title.text = [artwork.title stringByAppendingString:#"\n "];
cell.owner.text = [NSString stringWithFormat:#"%#",artwork.owner];
cell.fields.text = artwork.fields;
cell.numberOfViews.text = [NSString stringWithFormat:#"%#",artwork.views];
cell.numberOfLikes.text = [NSString stringWithFormat:#"%#",artwork.likes];
cell.numberOfComments.text = [NSString stringWithFormat:#"%#",artwork.comments];
cell.publishedDate.text = artwork.publishedDate;
if ([artwork.hasThumbnail boolValue]) {
dispatch_queue_t downloadQueue = dispatch_queue_create("imagecache", NULL);
dispatch_async(downloadQueue, ^{
UIImage *productImage = [UIImage imageWithData:artwork.thumbnail];
CGSize imageSize = productImage.size;
UIGraphicsBeginImageContext(imageSize);
[productImage drawInRect:CGRectMake(0, 0, imageSize.width, imageSize.height)];
productImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^ {
cell.thumbnailImage.image = productImage;
cell.thumbnailImage.hidden = NO;
});
});
}
else {
NSLog(#"Has to download thumbnail");
cell.thumbnailImage.hidden = YES;
[self startOperationsForPhotoRecord:artwork atIndexPath:indexPath];
}
if ([self.dataHelper.fetchedResultsController.fetchedObjects count]==self.numberOfLastItemOnPage) {
self.cellRefreshed = YES;
}
// it must be redifined for collections in behance!
if (indexPath.item >=(self.numberOfLastItemOnPage-10) &&self.cellRefreshed &&([self.projectsData count]%12==0)) {
self.numberOfFirstItemOnPage = self.numberOfLastItemOnPage+1;
self.numberOfLastItemOnPage +=12;
[self downloadMoreProjects];
self.cellRefreshed = NO;
}
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:CGRectZero];
cell.selectedBackgroundView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5f];
return cell;
}

Related

UICollectionViewCell memory leak when user click image loading all images(150) in slide objective c

Basically I want to display images in slide which are coming from server. it works for maximum 10 images. And I am displaying them in UICollectionView with Cell but when I click folder that has 100 +more images it gives me error that memory leak because it loads all images (3Gigabytes+) in one time when clicked. I tried to call by index it loads clicked image but when i swipe other images are not loading.
I resized all images with Bitmap all works but Image has too many details when zooming it's impossple to read. Is there any other way to load images one by one when Cell calls image?
Here is my LocalFileDeailViewController.m
(void) initPaging {
CGFloat width = self.scrollView.frame.size.width;
CGFloat height = self.scrollView.frame.size.height;
CGRect innerFrame = CGRectMake(0, 0, width, height);
for (int i = 0; i < self.fileList.count; i++) {
__block UIImage* Image = nil;
if ([self.ListType isEqual: #"url"]) {
NSData * imageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: self.fileList[i]]];
Image = [UIImage imageWithData: imageData];
} else {
NSString * filePath = (NSString *) self.fileList[i];
NSString *imageFullPath = [self.filePath stringByAppendingPathComponent:filePath];
BOOL isLoadAble = [[NSFileManager defaultManager] fileExistsAtPath:imageFullPath];
if ( isLoadAble ) {
Image = [UIImage imageWithContentsOfFile:imageFullPath];
} else {
//TODO 이미지가 없는경우 뭘 표시하나?
continue;
}
}
CGRect frame = CGRectMake(i * width, 0, width, height);
UIImageView *imgV = [[UIImageView alloc] initWithFrame:innerFrame] ;
[imgV setImage:Image];
[imgV setContentMode:UIViewContentModeScaleAspectFit];
[imgV setBackgroundColor:[UIColor blackColor]];
imgV.tag = VIEW_FOR_ZOOM_TAG;
UIScrollView *psv = [[UIScrollView alloc] initWithFrame:frame];
[psv setBackgroundColor:[UIColor blackColor]];
psv.minimumZoomScale = ZOOM_MIN;
psv.maximumZoomScale = ZOOM_MAX;
psv.zoomScale = ZOOM_SCALE;
psv.contentSize = imgV.bounds.size;
psv.delegate = self.scrollDelegate;
psv.showsHorizontalScrollIndicator = NO;
psv.showsVerticalScrollIndicator = NO;
[psv addSubview:imgV];
[self.contentView addSubview:psv];
[self.pages addObject:psv];
}
self.contentViewWidth.constant = self.fileList.count * width;
[self.scrollView setContentOffset:CGPointMake(width * self.index, 0) animated:NO];
And LocalImageGridViewController.m
#define CELL_ID #"LocalFileImageCollectionViewCell"
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
LocalFileImageCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CELL_ID forIndexPath:indexPath];
NSString * fileName = (NSString *) self.fileList[indexPath.row];
[ cell prepareForReuse];
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
[cell setImagePath:self.filePath imageName:fileName index: indexPath.row];
if (![cell superview]){
[_collectionView addSubview:cell];
}
return cell;
#pragma mark <UICollectionViewDelegate>
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
[collectionView deselectItemAtIndexPath:indexPath animated:NO]; NSLog(#"indexpath >>>> %#", indexPath);
// NSString * fileName = (NSString *) self.fileList[indexPath.row];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]];
LocalFileImageDetailViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"LocalFileImageDetailViewController"];
// [vc setFileName:fileName];
[vc setFileName:self.fileName];
[vc setFilePath:self.filePath];
[vc setFileList:self.fileList];
[vc setIndex:indexPath.item];
[self.navigationController pushViewController:vc animated:YES];
NSLog(#"vc >>>> %#", vc);
}
You should use UICollectionView in LocalFileDeailViewController instead of UIScrollView. Collection view load cells by demand, not on load view controller

Adaptable UICollectionViewCell Background image - locked/unlocked game levels

I have very little experience working with UICollectionView's but I've managed to create a scrollable interface that displays 100 buttons for 100 levels, 4 across, 25 down (well, they get generated on-the-fly as you know). The type of level is called Tortoise just so you're wondering what the hell that stands for later on. For Tortoise, there are only 20 levels.
Right now, the cell data string that I use to display the number gets placed over a regular background for the cell (indicating the level has been unlocked but not completed).
I have 2 other images I'd like to use as background images (one is a lock image where no number string appears, and the other is the same background as above just with a small checkmark for completion (along with the number string on top)).
To brief you, I'm using Core Data to keep track of a couple set of objects, along with whether a level is locked or unlocked. I call a method (right as I enter my Tortoise UICollectionView) called figureOutLocks which stores either a 0, 1, or 2 NSNumber object in this array self.lockArray. Here's that method:
- (void)figureOutLocks {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSManagedObjectContext* managedObjectContext = [(AppDelegate*)[[UIApplication sharedApplication]
delegate] managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Score" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entityDescription];
for (int i = 0; i < 20; i++) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K == %d AND %K == %d",
#"level", i, #"typeOfLevel", 0];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
Score* lockInfo = [[managedObjectContext executeFetchRequest:fetchRequest
error:&error] lastObject];
lockInfo.levelCompleted = [lockInfo valueForKey:#"levelCompleted"];
lockInfo.lockedLevel = [lockInfo valueForKey:#"lockedLevel"];
NSInteger complete = [lockInfo.levelCompleted integerValue];
NSInteger locked = [lockInfo.lockedLevel integerValue];
if ((locked == 0) && (complete == 0)) {
// level is unlocked but not complete (does not have any saved scores)
// lockArray gets a 0
[self.lockArray addObject:[NSNumber numberWithInteger:0]];
} else if ((locked == 1) && (complete == 0)) {
// level is locked which implies it is not complete
// lockArray gets a 1
[self.lockArray addObject:[NSNumber numberWithInteger:1]];
} else if ((locked == 0) && (complete == 1)) {
// level is complete thus it is unlocked
// lockArray gets a 2
[self.lockArray addObject:[NSNumber numberWithInteger:2]];
}
}
}
To brief you again, the first level is unlocked and not completed, and the other levels are locked (thus not completed).
Also, I created a NSArray *dataArray that contains string objects 1-20, and a NSArray *compareArray that contains NSNumber objects 1-20. My lockArray is NSMutableArray.
Furthermore, I decided to make 2 separate UICollectionViewCell subclasses in order to use both the regular background and the locked background. I didn't add the completion background subclass because I wanted to make sure the locked background works.
Here's the main method:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *regular;
static NSString *cellIdentifier = #"tortoiseCell";
static NSString *tortIdentifier = #"tortoiseLocked";
TortoiseCell *cell = (TortoiseCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
TortoiseLocked *lockCell = (TortoiseLocked *)[collectionView dequeueReusableCellWithReuseIdentifier:tortIdentifier forIndexPath:indexPath];
NSMutableArray *data = [self.dataArray objectAtIndex:indexPath.section];
NSString *cellData = [data objectAtIndex:indexPath.row];
NSMutableArray *locks = [self.compareArray objectAtIndex:indexPath.section];
NSNumber *locksData = [locks objectAtIndex:indexPath.row];
NSInteger locked = [locksData integerValue];
NSInteger lock = [[self.lockArray objectAtIndex:locked] integerValue];
if (lock == 0) {
[cell.buttonClick setTag:indexPath.row];
[cell.buttonClick setTitle:cellData forState:UIControlStateNormal];
[cell.buttonClick setBackgroundImage:[UIImage imageNamed:#"TortoiseLevels.png"]
forState:UIControlStateNormal];
[cell.buttonClick setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[cell.buttonClick.titleLabel setFont:[UIFont fontWithName:#"Arial Rounded MT Bold" size:25]];
[cell.buttonClick addTarget:self action:#selector(buttonPressedSoWhatNumber:)
forControlEvents:UIControlEventTouchUpInside];
cell.buttonClick.layer.cornerRadius = 8;
cell.buttonClick.layer.masksToBounds = YES;
[cell addSubview:cell.buttonClick];
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
regular = cell;
} else if (lock == 1) {
[lockCell.tortoiseLock setTag:indexPath.row];
[lockCell.tortoiseLock setBackgroundImage:[UIImage imageNamed:#"TortoiseLock.png"]
forState:UIControlStateNormal];
lockCell.tortoiseLock.layer.cornerRadius = 8;
lockCell.tortoiseLock.layer.masksToBounds = YES;
[lockCell addSubview:lockCell.tortoiseLock];
lockCell.layer.shouldRasterize = YES;
lockCell.layer.rasterizationScale = [UIScreen mainScreen].scale;
regular = lockCell;
}
return regular;
}
Is what I've done even possible? How do I make this work if it is? I tried using one UICollectionViewCell subclass and just programmatically change backgrounds but that didn't work, that's why you see what you see with the larger if statements. Any thoughts?
You definitely don't need to dequeue both cells from the table - you'll only ever be using one of them. Your code should be structured more like this:
static NSString *cellIdentifier = #"tortoiseCell";
static NSString *tortIdentifier = #"tortoiseLocked";
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *regular;
NSMutableArray *locks = [self.compareArray objectAtIndex:indexPath.section];
NSNumber *locksData = [locks objectAtIndex:indexPath.row];
NSInteger locked = [locksData integerValue];
NSMutableArray *data = [self.dataArray objectAtIndex:indexPath.section];
NSString *cellData = [data objectAtIndex:indexPath.row];
NSInteger lock = [[self.lockArray objectAtIndex:locked] integerValue];
if (lock == 0) {
TortoiseCell *cell = (TortoiseCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
[cell.buttonClick setTag:indexPath.row];
[cell.buttonClick setTitle:cellData forState:UIControlStateNormal];
[cell.buttonClick setBackgroundImage:[UIImage imageNamed:#"TortoiseLevels.png"]
forState:UIControlStateNormal];
[cell.buttonClick setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[cell.buttonClick.titleLabel setFont:[UIFont fontWithName:#"Arial Rounded MT Bold" size:25]];
[cell.buttonClick addTarget:self action:#selector(buttonPressedSoWhatNumber:)
forControlEvents:UIControlEventTouchUpInside];
cell.buttonClick.layer.cornerRadius = 8;
cell.buttonClick.layer.masksToBounds = YES;
[cell addSubview:cell.buttonClick];
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
regular = cell;
} else if (lock == 1) {
TortoiseLocked *lockCell = (TortoiseLocked *)[collectionView dequeueReusableCellWithReuseIdentifier:tortIdentifier forIndexPath:indexPath];
[lockCell.tortoiseLock setTag:indexPath.row];
[lockCell.tortoiseLock setBackgroundImage:[UIImage imageNamed:#"TortoiseLock.png"]
forState:UIControlStateNormal];
lockCell.tortoiseLock.layer.cornerRadius = 8;
lockCell.tortoiseLock.layer.masksToBounds = YES;
[lockCell addSubview:lockCell.tortoiseLock];
lockCell.layer.shouldRasterize = YES;
lockCell.layer.rasterizationScale = [UIScreen mainScreen].scale;
regular = lockCell;
}
return regular;
}
Also, since you have already set up two types of cell prototype on the storyboard, why bother setting each cell up programmatically? Just make them look how you want on the storyboard, and add the level number in the code.

IOS:app crash due to memory warning

My app gets crashed, when I am loading profile with thumbnail picture in UITableView.
It loads up to 5 profiles with picture properly. But, when I am creating sixth one, then loading profile in table view with picture it gets crashed, showing me the memory warning message.
Actually I have used ARC. So, I am not able to release the memory. I am only able to use the autoreleasepool to realease the memory.
Below is code I am using to load the profile in UITableView with profile picture.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
#autoreleasepool
{
static NSString *CellIdentifier=#"Cell";
UITableViewCell *cell = nil;
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] ;
}
MyProfile * objMP1 = [GlobalgroupedProfileArray objectAtIndex:[indexPath row]];
NSString *titleText =[#"Name: " stringByAppendingString:objMP1.Name];
cell.textLabel.text=titleText;
NSString *strTitle1=[#"Owner: "stringByAppendingString:[objMP1.Owner stringByAppendingString:#"\n"]];
NSString *strTitle2=[#"Breed: "stringByAppendingString:[objMP1.Breed stringByAppendingString:#"\n"]];
NSString *strTitle=[[strTitle1 stringByAppendingString:strTitle2] stringByAppendingString:[#"ID Number: "stringByAppendingString:[NSString stringWithFormat:#"%#",objMP1.IDNumber]]];
cell.detailTextLabel.text=strTitle;
cell.detailTextLabel.numberOfLines =3;
#autoreleasepool
{
UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(cell.frame.size.width-70-25, 15, 50, 50)];
imageView.image = objMP1.img;
[cell addSubview:imageView];
imageView=nil;
}
#autoreleasepool
{
// Load the image with an GCD block executed in another thread
dispatch_queue_t downloadQueue = dispatch_queue_create("image downloader", NULL);
dispatch_async(downloadQueue, ^{
CGSize newSize = CGSizeMake(50, 50); //whaterver size
UIGraphicsBeginImageContext(newSize);
[objMP1.img drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
#autoreleasepool
{
UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(cell.frame.size.width-70-25, 15, 50, 50)];
imageView.image = newImage;
[cell addSubview:imageView];
imageView=nil;
}
});
});
dispatch_release(downloadQueue);
}
[cell.imageView setImage:newImage];
cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
}
Any Hints?

cell label text changes on selection after scroll

I am having an issue with tableview cells that are created from 2 different prototype cells. I add my own labels and images to them.
They load fine, but after i scroll in the tableview, and then select a cell, the labels from other cells are getting added to the labels already present in the cell.
I've read about similar issues people are having but none address the fact that it only occurs on selection.
I have tried adding if (cell == nil){}; but that has no effect.
My cellForRowAtIndexPath is as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"NDSClassSubMenuViewController cellForRowAtIndexPath: running...");
NDSClassAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSDictionary *childNodes = [appDelegate.children objectAtIndex:0];
NSString *multicellstat = [childNodes objectForKey:#"#CELLTYPE"];
NSLog(#"Multicellstat %#", multicellstat);
NSLog(#"TESTING CHILD %#", childNodes);
//ONLY LOADING SUB MENU OPTIONS
NSString *cellType;
if (multicellstat == NULL){
NSLog(#"cellForRowAtIndexPath: #CellType == multicell. Making multicell.");
cellType = #"SegueCellSubmenu";
}else {
NSLog(#"cellForRowAtIndexPath: children variable is NOT NULL. Making MenuCell.");
cellType = #"SegueCellMulti";
}
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:cellType forIndexPath:indexPath];
if (multicellstat == NULL){
NSLog(#"Not adding picture.");
}else {
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this will start the image loading in bg
dispatch_async(concurrentQueue, ^{
UIView *blackBG = [[UIView alloc] initWithFrame:CGRectMake(5, 5, 60, 60)];
blackBG.backgroundColor = [UIColor grayColor];
NSURL *imageURL = [NSURL URLWithString:[childNodes objectForKey:#"#THUMBNAIL"]];
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.frame = CGRectMake(0, 0, 60, 60);
int borderWidth = 1;
imageView.frame = CGRectMake(borderWidth, borderWidth, blackBG.frame.size.width-borderWidth*2, blackBG.frame.size.height-borderWidth*2);
dispatch_async(dispatch_get_main_queue(), ^{
[cell addSubview:blackBG];
[blackBG addSubview:imageView];
});
});
}
//NSDictionary *tweet = [[[[appDelegate.menuItems objectForKey:#"Table"] objectForKey:#"Parent"]objectAtIndex:indexPath.row] objectForKey:#"Child"];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(70, 10, 210, 30)];
UILabel *detailText = [[UILabel alloc] initWithFrame:CGRectMake(70, 35, 210, 30)];
[cell.contentView addSubview:label];
[cell.contentView addSubview:detailText];
NSString *text = [[appDelegate.children objectAtIndex:indexPath.row] objectForKey:#"#MENUDESC"];
NSString *name = [[appDelegate.children objectAtIndex:indexPath.row] objectForKey:#"#MENUDESC"];
//NSLog(#"cellForRowAtIndexPath MENU ITEMS %#", text);
//Title label
label.text = NULL;
label.text = text;
[label setFont: [UIFont fontWithName:#"Arial" size:16.0]];
//Detail label
detailText.text = NULL;
detailText.text = name;
[detailText setFont: [UIFont fontWithName:#"Arial" size:12.0]];
//detailText.textColor = [UIColor colorWithRed:186.0/255.0 green:186.0/255.0 blue:186.0/255.0 alpha:1];
detailText.textColor = [UIColor grayColor];
//cell.textLabel.text = text;
//cell.detailTextLabel.text = [NSString stringWithFormat:#"by %#", name];
NSLog(#"Creating submenu item: %#", text);
NSLog(#"NDSClassSubMenuViewController: cellForRowAtIndexPath: finished.");
return cell;
}
Anyone know how I can solve this?
The key issue is that the code running synch should not assume that the cell it's modifying after the image request completes is the same cell (at the same indexPath) as when the image request began.
The correct recipe is like this:
use a cached result if we have one (so we don't do web requests every time we scroll), otherwise make an asynch request.
when the request completes, cache the result
use the original indexPath to determine if the cell is still visible. if it is, reload that indexPath. the newly cached result will now be available in cellForRowAtIndexPath.
Here's a snippet of code that describes how to correctly update at table after an asynch request. In your cellForRowAtIndexPath, check for a cached response before calling this:
// in cellForRowAtIndexPath:
NSString *urlString = [childNodes objectForKey:#"#THUMBNAIL"]
NSURL *imageURL = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:imageURL];
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
if (!cachedResponse) {
// we don't find this image/request in the cache, call asynch load
[self asynchLoad:urlString forIndexPath:indexPath];
} else {
// we got this image already, update the cell
UIImage *image = [UIImage imageWithData:cachedResponse.data];
// set the cell's UIImageView subview image to image
}
Here we load the image at urlString, cache the result, and then reload the indexPath after the load if it's still visible after the request completes:
- (void)asynchLoad:(NSString *)urlString forIndexPath:(NSIndexPath *)indexPath {
NSURL *imageURL = [NSURL URLWithString:[childNodes objectForKey:#"#THUMBNAIL"]];
NSURLRequest *request = [NSURLRequest requestWithURL:imageURL];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
// cache the response
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data];
[[NSURLCache sharedURLCache] storeCachedResponse:cachedResponse forRequest:self];
// important part - we make no assumption about the state of the table at this point
// find out if our original index path is visible, then update it, taking
// advantage of the cached image (and a bonus option row animation)
NSArray *visiblePaths = [self.tableView indexPathsForVisibleRows];
if ([visiblePaths containsObject:indexPath]) {
NSArray *indexPaths = [NSArray arrayWithObject:indexPath];
[self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation: UITableViewRowAnimationFade];
// because we cached the image, cellForRow... will see it and run fast
}
}
}];
}
I solved the problem by creating my own custom cell class and using storyboard to set the layout.

Weird updates when using reloadRowsAtIndexPaths: in a table

I've got a table where the accessoryView button in the cell is replaced by a custom check mark icon. If the time interval for a pair of temperature settings is enabled, the cell row shows a check mark, along with a time, heating and cooling value. If the time is disabled, no check mark, and time displayed in the timefield.text is modified to state: "Disabled". The code works fine when I use reloadData. However, since I'm updated only one row at a time, that's overkill for the big table that I'm using. I'm trying to use reloadRowsAtIndexPaths: but I get screen updates that affect not only the cell that was clicked on, but the previous cell that was clicked on. I was using no animations (UITableViewRowAnimationNone), but changed that to fading animations (UITableViewRowAnimationFade) to see what was going on. Sure enough, the fading occurs on the desired table row and the undesired table row. Also, the time value for the current cell row appears in the previous cell row time field. Once I stay with clicking on one row, there's no problem with the updates, but once I switch to another row, I have both rows affected the one time. It's like the update method is picking up cruft from somewhere.
I have also tried bracketing the reloadRowsAtIndexPaths: call with calls to beginUpdates and endUpdates, and nothing changes.
Clearly I'm not understanding something fundamental here. What am I doing wrong? Here is the method in question:
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
UIButton *button = (UIButton *)cell.accessoryView;
BOOL checked;
if (intervalDisabled[((indexPath.section * 4) + indexPath.row)] == NO) {
checked = YES;
intervalDisabled[((indexPath.section * 4) + indexPath.row)] = YES;
} else {
checked = NO;
intervalDisabled[((indexPath.section * 4) + indexPath.row)] = NO;
}
UIImage *newImage = (checked) ? [UIImage imageNamed:#"unchecked.png"] : [UIImage imageNamed:#"checked.png"];
[button setBackgroundImage:newImage forState:UIControlStateNormal];
timeField.text = [NSString stringWithFormat:#"%#", [self.dateFormatter stringFromDate:[timeOfDays objectAtIndex:((indexPath.section * 4) + indexPath.row)]]];
//[self.tableView reloadData]; <-- This works fine
NSArray *rowArray = [NSArray arrayWithObject:indexPath];
[self.tableView reloadRowsAtIndexPaths:rowArray withRowAnimation:UITableViewRowAnimationFade];
}
Edit:
OK. Here is code you requested. I hope it helps. I will be glad to post other portions. Sorry, I haven't got the hang for formatting code in this editor yet.
// Customized cell for setpoints
- (UITableViewCell *)setPointsCell:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *temperatureSymbol;
static NSString *kCustomCellID = #"setPointsCellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCustomCellID];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kCustomCellID] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
}
// Set up the cell.
NSDictionary *dictionary = [listOfRows3 objectAtIndex:indexPath.section];
NSArray *array = [dictionary objectForKey:#"Content"];
NSString *cellValue = [array objectAtIndex:indexPath.row];
//Set properties of label and add its text
cell.textLabel.font = [UIFont systemFontOfSize:[UIFont labelFontSize]];
cell.textLabel.text = cellValue;
// Add time field
timeField = [[UITextField alloc] init];
timeField.tag = (indexPath.section * 100) + (indexPath.row * 10) + TIME_FIELD;
CGRect frame = CGRectMake(80.0, 6.0, 90.0, 31.0);
timeField.frame = frame;
timeField.borderStyle = UITextBorderStyleRoundedRect;
timeField.font = [UIFont systemFontOfSize:[UIFont labelFontSize]];
// timeField.backgroundColor = [UIColor clearColor];
timeField.textAlignment = UITextAlignmentRight;
// Check and handle situation where time interval is disabled
BOOL rowHasCheck;
if (intervalDisabled[((indexPath.section * 4) + indexPath.row)] == NO) {
rowHasCheck = YES;
timeField.text = [NSString stringWithFormat:#"%#", [self.dateFormatter stringFromDate:[timeOfDays objectAtIndex:((indexPath.section * 4) + indexPath.row)]]];
} else {
rowHasCheck = NO;
timeField.text = #"Disabled";
}
UIImage *image = (rowHasCheck) ? [UIImage imageNamed:#"checked.png"] : [UIImage imageNamed:#"unchecked.png"];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
CGRect bFrame = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
button.frame = bFrame; // match the button's size with the image size
[button setBackgroundImage:image forState:UIControlStateNormal];
// set the button's target to this table view controller so we can interpret touch events and map that to a NSIndexSet
[button addTarget:self action:#selector(checkButtonTapped:event:) forControlEvents:UIControlEventTouchUpInside];
button.backgroundColor = [UIColor clearColor];
cell.accessoryView = button;
// Override default keyboard handler so we can use a picker instead
timeField.delegate = self;
[cell.contentView addSubview:timeField];
// Set up termperature (F or C) to display
if (temperatureScale == FAHRENHEIT) {
temperatureSymbol = #"F";
} else {
temperatureSymbol = #"C";
}
// Add heating setpoint field
UITextField *heatingSetPointField = [[UITextField alloc] init];
heatingSetPointField.tag = (indexPath.section * 100) + (indexPath.row * 10) + HEATING_FIELD;
frame = CGRectMake(180.0, 6.0, 52.0, 31.0);
heatingSetPointField.frame = frame;
heatingSetPointField.borderStyle = UITextBorderStyleBezel;
heatingSetPointField.backgroundColor = [UIColor colorWithRed:1.0 green:0.2 blue:0.2 alpha:1.0];
heatingSetPointField.font = [UIFont systemFontOfSize:[UIFont labelFontSize]];
heatingSetPointField.textAlignment = UITextAlignmentRight;
heatingSetPointField.text = [NSString stringWithFormat:#"%i %#", hSetpoints[((indexPath.section * 4) + indexPath.row)], temperatureSymbol];
// Override default delegate handler
heatingSetPointField.delegate = self;
[cell.contentView addSubview:heatingSetPointField];
// Add cooling setpoint field
UITextField *coolingSetPointField = [[UITextField alloc] init];
coolingSetPointField.tag = (indexPath.section * 100) + (indexPath.row * 10) + COOLING_FIELD;
frame = CGRectMake(240.0, 6.0, 52.0, 31.0);
coolingSetPointField.frame = frame;
coolingSetPointField.borderStyle = UITextBorderStyleBezel;
coolingSetPointField.font = [UIFont systemFontOfSize:[UIFont labelFontSize]];
coolingSetPointField.backgroundColor = [UIColor colorWithRed:0.4 green:0.4 blue:1.0 alpha:1.0];
coolingSetPointField.textAlignment = UITextAlignmentRight;
coolingSetPointField.text = [NSString stringWithFormat:#"%i %#", cSetpoints[((indexPath.section * 4) + indexPath.row)], temperatureSymbol];
// Override default delegate handler
coolingSetPointField.delegate = self;
[cell.contentView addSubview:coolingSetPointField];
[timeField release];
[heatingSetPointField release];
[coolingSetPointField release];
return cell;
}
The main problem is that you seem to be using a single timeField reference across all cells. At a minimum, the timeField should be created separately in each cell (just like the heatingSetPointField) and then extract it from the cell later using its tag.
There are many other issues (eg. text fields will get added multiple times to each cell when a cell is reused).
I think reloadData is "working" because you haven't tried scrolling the table view up/down and seeing wrong data in the cells when they come back into view.
You may want to read the Table View Programming Guide (especially the Customizing Cells section) and start with a simpler table view cell and add one layer of complexity at a time until you reach your goal.