I'n new in iPhone, I'm trying to delete a cell from my UITableView, the first time it deletes well, but the second time it gives me the following error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'Invalid update: invalid number of rows in section 0. The number of rows
contained in an existing section after the update (3) must be equal to the number
of rows contained in that section before the update (3), plus or minus the number
of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or
minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
here is my code of table:
- (void)tableView:(UITableView *)tableView commitEditingStyle: (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
Book_own *temp= (Book_own *)[self.books objectAtIndex:indexPath.row];
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[books removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
isDeleted = #"true";
deletedBook_id = temp.bo_id;
[self viewDidLoad];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
NSDictionary *dictionary = [listOfItems objectAtIndex:section];
NSArray *array = [dictionary objectForKey:#"myBooks"];
return [array count];
}
in ViewDidLoad I wrote the following code:
NSDictionary *mbooks = [NSDictionary dictionaryWithObject:books forKey:#"myBooks"];
NSDictionary *mgroups = [NSDictionary dictionaryWithObject:filteredParts forKey:#"myBooks"];
listOfItems = [[NSMutableArray alloc] init];
[listOfItems addObject:mbooks];
[listOfItems addObject:mgroups];
Can anyone tell me how to solve this issue??
Thanks in advance.
What is happening is that you are either not deleting the item or you are deleting only one item when you allow multible deleting. You can adjust by checking if it is really deleted as below or reload data if not:
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
BOOL success = [self removeFile:indexPath];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
if (!success) [tableView reloadData];
}
}
This method then removes the datasource item (it is from my own project so the names should be adjusted:
-(BOOL) removeFile:(NSIndexPath *)indexPath{
// Removes from the datasource and the filesystem
NSURL *fileURL = [self.dataSource objectAtIndex:indexPath.row];
NSError *error;
BOOL success = [[NSFileManager defaultManager] removeItemAtURL:fileURL error:&error];
if (success) {
[self.dataSource removeObjectAtIndex:indexPath.row];
} else {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Error" message:[error localizedDescription] delegate:self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
[alert release];
[self.dataSource removeObjectAtIndex:indexPath.row];
}
return success;
}
If I'm reading your code correctly your data source is listOfItems. You should remove the row from your tables data source. A general rule is that when you remove or add items to a UITableView you must update the databsource.
[listOfItemsremoveObjectAtIndex:indexPath.row];
First, do not call [self viewDidLoad]; This method should not be called manually.
I think you are not calling the update method to your table view. This might fix your problems:
[tableView beginUpdates];
[books removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
edit: you also need to look at your code more closely. You are deleting a record from your datasource array directly from the row of the indexPath, which could be problematic considering the fact that your tableView has two sections.
The error says that there should be 1 fewer row after the deletion. It fails because the datasource code is not consistent in how it reports about the model.
Look at how you answer numberOfRowsInSection. Correctly, I think, first picking out the array that the section index represents, then answering that array's count.
The same logic must apply on the delete. The array you delete from needs to be the array indicated by the indexPath.section. Consider this:
- (void)tableView:(UITableView *)tableView commitEditingStyle: (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
// the array you manipulate here must be the same array you use to count
// number of rows, and the same you use to render rowAtIndexPath
NSDictionary *dictionary = [listOfItems objectAtIndex:indexPath.section];
NSArray *array = [dictionary objectForKey:#"myBooks"];
// in order to be edited, it must be mutable
// you can do that on the fly here, or set it up as mutable
// let's make it temporarily mutable here:
NSMutableArray *mutableCopy = [array mutableCopy];
if (editingStyle == UITableViewCellEditingStyleDelete) {
[mutableCopy removeObjectAtIndex:indexPath.row];
// if it's setup mutable, you won't need to replace the immutable copy in the dictionary.
// but we just made a copy, so we have to replace the original
[listOfItems replaceObjectAtIndex:indexPath.section withObject:[NSArray arrayWithArray:mutableCopy]];
// and so on
Related
I have a sorted NSMutableArray which works perfectly and all though when I try to delete an object it crashes the app then when I reload the app it didn't delete the right one.
I known that is due to the fact that this is now a sorted array because before I implemented this feature it worked fine though I haven't got a clue of how to fix it.
Here is the code I use to delete things from the array:
- (void) tableView:(UITableView *)tv commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if ( editingStyle == UITableViewCellEditingStyleDelete ) {
Patient *thisPatient = [patients objectAtIndex:indexPath.row];
[patients removeObjectAtIndex:indexPath.row];
if (patients.count == 0) {
[super setEditing:NO animated:YES];
[self.tableView setEditing:NO animated:YES];
}
[self deletePatientFromDatabase:thisPatient.patientid];
[tv deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
}
}
It is being stopped at this line:
[tv deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
Here is the code that I use for sorting the array:
-(void) processForTableView:(NSMutableArray *)items {
for (Patient *thisPatient in items) {
NSString *firstCharacter = [thisPatient.patientName substringToIndex:1];
if (![charIndex containsObject:firstCharacter]) {
[charIndex addObject:firstCharacter];
[charCount setObject:[NSNumber numberWithInt:1] forKey:firstCharacter];
} else {
[charCount setObject:[NSNumber numberWithInt:[[charCount objectForKey:firstCharacter] intValue] + 1] forKey:firstCharacter];
}
}
charIndex = (NSMutableArray *) [charIndex sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
}
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:#"cell"];
NSString *letter = [charIndex objectAtIndex:[indexPath section]];
NSPredicate *search = [NSPredicate predicateWithFormat:#"patientName beginswith[cd] %#", letter];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"patientName" ascending:YES];
NSArray *filteredArray = [[patients filteredArrayUsingPredicate:search] sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
if ( nil == cell ) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
NSLog(#"indexPath.row = %d, patients.count = %d", indexPath.row, patients.count);
Patient *thisPatient = [filteredArray objectAtIndex:[indexPath row]];
cell.textLabel.text = [NSString stringWithFormat:#"%# %#", thisPatient.patientName, thisPatient.patientSurname];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.textColor = [UIColor blackColor];
if (self.editing) {
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}
return cell;
}
I suspect this is quite a common thing that happens when you sort an array though it may not be.
Please say if you want any more code
It's not neccessarily because your array is sorted, but because the array you are populating your table from is not the same as the array that you are removing the object from.
Where are you sorting the patients array? Is the actual patients array being sorted or are you sorting it in your tableView delegates method and not actually sorting patients?
The reason for this is that the index of the object you deleted is not the same as the index that it has in the actual patients array (because one is sorted and one is not). Because of this, it is first deleting the wrong object, then it crashes because the tableView expects one to be deleted (so that it can animate that cell being removed) but the wrong one was deleted.
I am trying to override tableView:commitEditingStyle:editingStyleforRowAtIndexPath: and having trouble implementing the deletion of the actual instance of a NSManagedObject that is represented in that row.
Apple says it should be done with the following code(Shown Here):
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the managed object at the given index path.
NSManagedObject *eventToDelete = [eventsArray objectAtIndex:indexPath.row];
[managedObjectContext deleteObject:eventToDelete];
// Update the array and table view.
[eventsArray removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
// Commit the change.
NSError *error = nil;
if (![managedObjectContext save:&error]) {
// Handle the error.
}
}
}
When I mimick this sample code in my app, every line works except for one line. The one line is: [bowlerArray removeObjectAtIndex:indexPath.row];. I get the error "Receiver type 'NSArray' for instance message does not declare a method with selector 'removeObjectAtIndex'".
What should that one line of code be?
Note: My line NSManagedObject *eventToDelete = [bowlerArray objectAtIndex:indexPath.row]; works just fine.
Update: Posting my actual code:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = [appDelegate managedObjectContext];
NSManagedObject *objectToDelete = [bowlerArray objectAtIndex:indexPath.row];
[moc deleteObject:objectToDelete];
[bowlerArray removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
NSArray is immutable, so you can't modify it.
removeObjectAtIndex is not part of the NSArray API, because it would modify it.
You need an NSMutableArray to be able to do that.
If I do this :
NSMutableArray *arMu = [NSMutableArray arrayWithObjects:#"0", #"1", #"2", #"3", #"4", nil];
[arMu removeObjectAtIndex:0];
self.bigLabel.text = [arMu objectAtIndex:0];
the bigLabel is showing 1 for the index 0.
You error message is suggesting that you still have a NSArray instead of an NSMutableArray for the variable eventsArray
You can make a NSMutableArray from a NSArray like this :
NSMutableArray *arMu = [[NSMutableArray alloc] initWithArray:someNSArray];
Problem: When I click the delete button for a given table/section row, i get the following error: "*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (4) must be equal to the number of rows contained in that section before the update (4), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted).'"
From other posts I have read about this symptom, I gather I am suppose to be manually removing an element in my datasource array, but not sure how to access the section's array inside this method:
// COMMIT EDITING STYLE
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"indexPath: %#", indexPath);
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates]; // throws error here
[tableView reloadData];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
I think the complication for this situation arises due to the fact that the plist (FormEntries.plist) I am pulling data from holds user input for all sorts of things all through out my app, thus I am having to call and filter it for every section. This works fine to populate the UITableView and all of it's sections, but because a new filtered array is being created for and inside each section, I'm not sure how to ever access it again in order to remove the element, thus rectifying the above error message. Here is how I am loading the data for each table section:
// CELL FOR ROW AT INDEXPATH
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath (NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
NSNumber *numScreenId = [[arrayOfModulesScreens objectAtIndex: indexPath.section] objectForKey: #"id"];
NSMutableArray *arrayRecords = [epFrameWork selectPlist: #"FormEntries" filterByKey: #"screen_id" keyValue:numScreenId];
NSString *strTitle = [[arrayRecords objectAtIndex: indexPath.row] objectForKey: #"storage_string"];
cell.textLabel.text = strTitle;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
-- Not sure if this will help diagnose things, but here it is none the less ---
// TITLE FOR HEADER IN SECTION
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [[arrayOfModulesScreens objectAtIndex: section] objectForKey: #"screen_title"];
}
// NUMBER OF SECTIONS IN TABLE VIEW
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [arrayOfModulesScreens count];
}
// NUMBER OF ROWS IN SECTION
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSNumber *numScreenId = [[arrayOfModulesScreens objectAtIndex: section] objectForKey: #"id"];
NSMutableArray *arrayRecords = [epFrameWork selectPlist: #"FormEntries" filterByKey: #"screen_id" keyValue:numScreenId];
int rowCount = [arrayRecords count];
return rowCount;
}
What is the best approach to handle this situation or to resolve the above posted error message?
-- UPDATE --
So here is how I'm trying to identify which plist record to delete, assuming that's what I need to do to resolve the original error:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
int g = indexPath.row;
int count = -1;
UITableViewCell *tvc = [[UITableViewCell alloc] init];
for(id element in tableView.subviews) {
if([element isKindOfClass:[UITableViewCell class]]) {
count +=1;
NSLog(#"g: %d - count: %d", g , count);
if(count == g) {
tvc = element;
NSLog(#"tvc: %# - UID: %# - g: %d - count: %d", tvc, tvc.detailTextLabel.text, g , count);
}
}
}
My logic here was to set a hidden unique identifier on tvc.detailTextLabel.text in the cellForRowAtIndexPath method, which in turn would let me know which record from the plist to filter and delete by calling [array removeObjectAtIndex:uid] where array is my filtered plist array. Only problem now is that tvc in the NSLog always returns the record at index 0, not the row that holds the delete button I click.
NSLog returns: tvc: < UITableViewCell: 0x713e2c0; frame = (0 30; 320 44); text = 'Church A'; autoresize = W; layer = < CALayer: 0x7113e70 > > - UID: -237206321 - g: 3 - count: 3. So why would tvc return the index 0 when it was index 3 I clicked the delete button?
Is this just becoming a clustered mess or is there a cleaner solution? But ya, still stumped.
This error most definitely relates to your mishandling the data that you are trying to load to your table. I found that the easiest and safest way to handle modifying table content is to do something along those lines, with the necessary adjustments (within tableView:commitEditingStyle:)
//REMOVE A CELL FROM A SECTION
[yourTable beginUpdates];
[yourTable deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationBottom];
[yourTable endUpdates];
[yourTable reloadData];
In addition you need to make sure that your array is properly updated to have the changes reflected in the table.
This is how I was finally able to resolve the issue:
I changed all of this crap:
int g = indexPath.row;
int count = -1;
UITableViewCell *tvc = [[UITableViewCell alloc] init];
for(id element in tableView.subviews) {
if([element isKindOfClass:[UITableViewCell class]]) {
count +=1;
NSLog(#"g: %d - count: %d", g , count);
if(count == g) {
tvc = element;
NSLog(#"tvc: %# - UID: %# - g: %d - count: %d", tvc, tvc.detailTextLabel.text, g , count);
}
}
}
to one simple line:
UITableViewCell *cell = [[self tableView] cellForRowAtIndexPath:indexPath];
This allowed me to identify the cell I was working with. So my final code that worked looks like this:
// COMMIT EDITING STYLE
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
UITableViewCell *cell = [[self tableView] cellForRowAtIndexPath:indexPath];
[epFrameWork deleteRecordFromPlist:#"FormEntries" uid:cell.detailTextLabel.text];
[tableView reloadData];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
-(void) deleteRecordFromPlist:(NSString *)plist uid:(NSString *)uId {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *tmpFileName = [[NSString alloc] initWithFormat:#"%#.plist", plist];
NSString *path = [documentsDirectory stringByAppendingPathComponent:tmpFileName];
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:path];
NSDictionary *dict = [[NSDictionary alloc] init];
NSString *tmpUid;
for(int i=0; i < [array count]; i++) {
dict = [array objectAtIndex:i];
tmpUid = [dict valueForKey:#"uid"];
if([tmpUid isEqualToString:uId]) {
[array removeObjectAtIndex:i];
[array writeToFile:path atomically:YES];
}
}
}
I am working on a todo list application with CoreData + UITableView, I would like to hide the row that the user mark as done.
My current solution is invoke deleteRowsAtIndexPaths when user mark the task done and deduct the deleted row from the function of numberOfRowsInSection.
-(void)markDone:(NSIndexPath *) _indexPath{
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:_indexPath] withRowAnimation:UITableViewRowAnimationFade];
deletedCount = deletedCount + 1;
[self.tableView endUpdates];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
if (deletedCount>0) {
return [sectionInfo numberOfObjects]-deletedCount;
}
return [sectionInfo numberOfObjects];
}
Although this method does work, but I do need some code hacking here and there. Is there a way to invoke NSFetchedResultsController didChangeObject for changing of status of particular field?
Thanks
I think there are many ways to solve this. I'd just add a field in your managed object which states if a row is hidden or not"
Deleting will set this field accordingly.
What you need now is an NSFetchRequest with the corresponding predicate to filter hidden rows.
I just created a simple template app with core data support, and I think it is very easy to achieve:
I added a hidden BOOL property to the one entitiy given, with default NO.
Then I added this code to didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSManagedObject *selectedObject = [[self fetchedResultsController] objectAtIndexPath:indexPath];
[selectedObject setValue:[NSNumber numberWithBool:YES] forKey:#"hidden"];
}
In - (NSFetchedResultsController *)fetchedResultsController {... I added
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"hidden == NO"];
[fetchRequest setPredicate:predicate];
after
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
This was it to hide cells by clicking(just for this example) on them.
I'm using an NSFetchedResultsController to display items in my table view:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [[self.fetchedResultsController fetchedObjects] count];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"TagCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];;
}
Tag *tag = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = tag.name;
return cell;
}
However, this code breaks at this line:
Tag *tag = [self.fetchedResultsController objectAtIndexPath:indexPath];
With this message:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSCFArray objectAtIndex:]: index (0) beyond bounds (0)'
I've NSLogged [self.fetchedResultsController fetchedObjects] and I can confirm that there are indeed Tag objects. If I replace that above line with this everything works as expected:
Tag *tag = [[self.fetchedResultsController fetchedObjects] objectAtIndex:indexPath.row];
I NSLogged indexPath and the indexes are {0, 0} (section 0 row 0) so I know it isn't an issue with the section. I'm extremely confused as to why this is happening because theoretically, those two pieces of code do the same thing. Any help is appreciated.
Thanks
UPDATES:
id section = [[[self fetchedResultsController] sections] objectAtIndex:[indexPath section]];
NSLog(#"Section %#", section); <-- returns a valid section
This code results in the same exception: Tag *tag = [[section objects] objectAtIndex:[indexPath row];
If I NSLog [section objects] it returns an empty array. I'm not sure why [fetchedResultsController fetchedObjects] returns an array with the right objects, and [section objects] returns nothing. Does this mean that the objects I'm creating have no section? Here's the code that I use to add new objects:
- (void)newTagWithName:(NSString *)name
{
NSIndexPath *currentSelection = [self.tableView indexPathForSelectedRow];
if (currentSelection != nil) {
[self.tableView deselectRowAtIndexPath:currentSelection animated:NO];
}
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
Tag *newTag = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:self.managedObjectContext];
// Configure new tag
newTag.name = name;
[self saveContext];
NSIndexPath *rowPath = [self.fetchedResultsController indexPathForObject:newTag];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:rowPath] withRowAnimation:UITableViewRowAnimationTop];
[self.tableView selectRowAtIndexPath:rowPath animated:YES scrollPosition:UITableViewScrollPositionTop];
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
}
And here's my saveContext method:
- (void)saveContext
{
// Save changes
NSError *error;
BOOL success = [self.managedObjectContext save:&error];
if (!success)
{
UIAlertView *errorAlert = [[[UIAlertView alloc] initWithTitle:#"Error encountered while saving." message:nil delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil] autorelease];
[errorAlert show];
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
Am I doing something wrong here?
I ran into the same problem. In my case, it seems to be a corrupt cache file. Try renaming your cache, or calling deleteCacheWithName:.
When you initialized your fetch request controller, you gave it a sectionNameKeyPath: even though your data has no sections. Then you hard coded your tables section number to 1.
The fetch request controller is trying to return an object at section index zero in a data structure that has no sections at all.
I reproduced your error in the default navigation template app by changing the sectionNameKeyPath from nil to the name of the entity's sole attribute.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:#"timeStamp" cacheName:#"Root"];
... and then changed
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// return [[fetchedResultsController sections] count];
return 1;
}
and I get:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSCFArray objectAtIndex:]: index (0) beyond bounds (0)'
Set the sectionNameKeyPath:nil and that should fix your problem.
Are you compiling against, targeting or testing on 3.0?
What do you get back from the following code:
id section = [[[self fetchedResultsController] sections] objectAtIndex:[indexPath section]];
NSLog(#"Section %#", section);
Are you getting a valid section back?
If so, try adding the following line:
Tag *tag = [[section objects] objectAtIndex:[indexPath row];
If that produces an error then I suspect the issue is in your -tableView: numberOfRowsInSection: and that it may be giving back the wrong number of rows to the UITableView. Can you edit your question and post the code for that method as well?
Update
On further review I see where TechZen was pointing. You are telling the table view that you have one section when you may not. Further you are telling it how many objects your entire NSFetchedResultsController has when you should be answering it a little more succinctly.
Here is a slight change to your code.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[[self fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}
Make those changes and see what impact that has.
Question
What other UITableViewDataSource methods have you implemented? Sounds like there may be at least one more lingering error in your implementation.
got this error last night and it drove me crazy. I found that if you use the storyboard to specify the number of rows and sections, it needs to match the number you specify in your data source methods. To completely fix the error, you can set everything to 0 in the attributes inspector and just do it all programmatically, or just make sure that both data source methods and attributes inspector reflect the same amount of rows and sections.
^^