Problem with NSFetchedResultsController and inserting records
Ok, this one is driving me crazy and I've tried everything I can find on the internet as a possible solution but still nothing. So here is my situation: I have a view that has a button on it. When this is touched it pops up a list of customers for the user to select from. when they select it, i make a use the fetchedresultscontroller to get the parts associated to the customer and display them in a tableview. This all works well. the problem is if I have Customer A selected and insert a new part, then go to Customer B and insert a new part, when i reselect Customer A and try to insert another part the app crashes with the following error:
* Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1447.6.4/UITableView.m:976 2011-02-21 10:39:12.896 SalesPro[36203:207] Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted). with userInfo (null) 2011-02-21 10:39:12.907 SalesPro[36203:207] * Terminating app due to uncaught exception 'NSRangeException', reason: '-[UITableView scrollToRowAtIndexPath:atScrollPosition:animated:]: row (1) beyond bounds (1) for section (0).'
Code that handles selecting of Customer
-(void) CustomerSelectedRaised:(NSNotification *)notif
{
NSLog(@"Received Notification - Customer Selected");
selectedCustomer = (Customer *)[notif object];
[self buildCustomerInfoText];
if (popoverController != nil) {
[popoverController dismissPopoverAnimated:YES];
}
fetchedResultsController = nil;
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
//Update to handle error appropriately
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
exit(-1); //fail
}
[self.partsListGrid reloadData];
}
FetchedResultsController code
#pragma mark -
#pragma mark Fetch results controller
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
//set-up fetched results controller
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"PartsList" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSLog(@"TAMS ID: %@", selectedCustomer.customerTAMSID);
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"customerTAMSID == %@", selectedCustomer.customerTAMSID]];
//set to sort by customer name
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"sortOrder" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:[self managedObjectContext]
sectionNameKeyPath:nil cacheName:nil];
[aFetchedResultsController setDelegate:self];
[self setFetchedResultsController:aFetchedResultsController];
//clean-up
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
//return results
return fetchedResultsController;
}
- (void)controllerWillChangeContent:(NSFetchedResultsController*)controller
{
[[self partsListGrid] beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// In the simplest, most efficient, case, reload the table view.
[[self partsListGrid] endUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.partsListGrid;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath withHeight:tableView.rowHeight];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.partsListGrid insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.partsListGrid deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
break;
case NSFetchedResultsChangeUpdate:
break;
default:
break;
}
}
Add Part code
-(void) addScannedPart:(Part 开发者_JAVA技巧*)part
{
// Check to see if entered part is already in list
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *partsEntity = [NSEntityDescription entityForName:@"PartsList" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:partsEntity];
NSPredicate *predicate = [NSPredicate predicateWithFormat: @"customerTAMSID == %@ AND lineAbbreviation == %@ AND partNumber == %@", selectedCustomer.customerTAMSID, part.lineAbbrev, part.partNumber];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchedParts = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if ([fetchedParts count] == 0) {
//Create a new instance of the entity managed object by the fetched results controller
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity];
NSLog(@"Entity Name: %@", [entity name]);
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
//Add fields to Managed Object
int sortOrder = [[fetchedResultsController fetchedObjects] count];
sortOrder++;
[newManagedObject setValue:[part lineAbbrev] forKey:@"lineAbbreviation"];
[newManagedObject setValue:[part partNumber] forKey:@"partNumber"];
[newManagedObject setValue:[NSNumber numberWithInt:[[part orderQty] intValue]] forKey:@"orderQuantity"];
[newManagedObject setValue:selectedCustomer.customerTAMSID forKey:@"customerTAMSID"];
[newManagedObject setValue:[NSNumber numberWithInt:sortOrder] forKey:@"sortOrder"];
//Save the context
NSError *error = nil;
if (![context save:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
//reload Customer list
NSIndexPath *insertionPath = [fetchedResultsController indexPathForObject:newManagedObject];
[self.partsListGrid selectRowAtIndexPath:insertionPath animated:YES scrollPosition:UITableViewScrollPositionTop];
[self.partsListGrid reloadData];
}
}
This is the biggest (most severe) defect I have to figure out for our next release (which is very soon). I'd appreciate any and all help! Thanks!
start with fixing the leaking NSFetchedResultsCOntroller in
-(void) CustomerSelectedRaised:(NSNotification *)notif
{
/*...*/
fetchedResultsController = nil;
/*...*/
}
you shouldn't have multiple NSFetchedResultsControllers that all report changes to the same tableview. And I think this is what is happening. Unless the NSFRController gets deallocated it will report changes to its delegate
ok, thanks to this SO thread, it looks like I have it working:
I edited by CustomerSelectedRaised method to look like so:
-(void) CustomerSelectedRaised:(NSNotification *)notif
{
NSLog(@"Received Notification - Customer Selected");
selectedCustomer = (Customer *)[notif object];
[self buildCustomerInfoText];
if (popoverController != nil) {
[popoverController dismissPopoverAnimated:YES];
}
//this is the new code
NSFetchRequest *fetchRequest = [[self fetchedResultsController] fetchRequest];
NSEntityDescription *entity = nil;
entity = [NSEntityDescription entityForName:@"PartsList" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"customerTAMSID == %@", selectedCustomer.customerTAMSID]];
[NSFetchedResultsController deleteCacheWithName:nil];
// end of new code
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
//Update to handle error appropriately
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
exit(-1); //fail
}
[self.partsListGrid reloadData];
}
精彩评论