iphone uitableview core-data nsfetchedresultscontroller nsmanagedobject

iphone - Error de CoreData volviéndome loco... CoreData: Error grave de la aplicación. Una excepción capturada del delegado de NSFetchedResultsController



uitableview core-data (1)

Mi aplicación tiene dos barras de pestañas ... Cada una lleva al usuario a un controlador de vista de tabla que le presenta una lista de elementos. La primera vista permite al usuario registrar entradas en la base de datos. La otra pestaña / vista se lee de la base de datos y también presenta esos elementos al usuario; sin embargo, no se realizan actualizaciones en el CoreData / persistant store desde esta segunda vista.

Cuando agrego un nuevo elemento a través del primer controlador de vista, aparece perfectamente en la vista. Sin embargo, tan pronto como toco en la otra barra de pestañas para ver el nuevo elemento que aparece en ese controlador de vista, aparece el siguiente error y el elemento recién agregado no aparece ... Nota: si detengo la aplicación y vuelvo a cargar / vuelva a ejecutarlo y comience tocando la 2ª barra de pestañas; el nuevo elemento se mostrará bien, así sé que el modelo se está actualizando correctamente.

*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1912.3/UITableView.m:1046 2011-10-20 20:56:15.117 Gtrac[72773:fb03] CoreData: error: 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 (4) 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, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)

Código de la aplicación delegada donde managedObjectContext se pasa a los dos viewControllers.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // get a point to the master database context NSManagedObjectContext *context = [self managedObjectContext]; if (!context) { // Handle the error. } // create tab bar controller and array to hold each of the tab-based view controllers that will appear as icons at bottom of screen tabBarController = [[UITabBarController alloc] init]; NSMutableArray *localControllersArray = [[NSMutableArray alloc] initWithCapacity:5]; // // setup first tab bar item // // // alloc the main view controller - the one that will be the first one shown in the navigation control RootViewController *rootViewController = [[RootViewController alloc] initWithTabBar]; // Pass the managed object context to the view controller. rootViewController.managedObjectContext = context; // create the navigation control and stuff the rootcontroller inside it UINavigationController *aNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController]; // set the master navigation control self.navigationController = aNavigationController; // add the navigaton controller as the first tab for the tab bar [localControllersArray addObject:aNavigationController]; [rootViewController release]; [aNavigationController release]; // // setup the other tab bar // // // alloc the view controller vcSimulator *vcSimulatorController = [[vcSimulator alloc] initWithTabBar]; UINavigationController *blocalNavigationController = [[UINavigationController alloc] initWithRootViewController:vcSimulatorController]; // Pass the managed object context to the view controller. vcSimulatorController.managedObjectContext = context; // add this controller to the array of controllers we are building [localControllersArray addObject:blocalNavigationController]; // release these guys, they are safely stored in the array - kill these extra references [blocalNavigationController release]; [vcSimulatorController release]; // // // ok, all the tab bars are in the array - get crackin // // // load up our tab bar controller with the view controllers tabBarController.viewControllers = localControllersArray; // release the array because the tab bar controller now has it [localControllersArray release]; [window addSubview:[tabBarController view]]; [window makeKeyAndVisible]; return YES; When I add a new item via the first viewcontroller, it shows up perfectly in the view. However, as soon as I tap on the other tab bar to see the new item appear in that viewcontroller, I get the error listed above, and the newly added item does not appear... Note: if I stop the app and reload/re-run it, and start by tapping the 2nd tabbar, the new item shows up fine, so I know the model is being updated fine. Here are the tableview delegate methods from the 2nd view controller. - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { [self.tableView beginUpdates]; } - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__); UITableView *tableView = self.tableView; 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]; 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 { NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__); switch(type) { case NSFetchedResultsChangeInsert: [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; } } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { [self.tableView endUpdates]; }

Cualquier ayuda que pueda proporcionar sería muy apreciada.

He buscado en este sitio y encontré muchas instancias de este error, pero ninguno parece encajar. También he visto referencias que infieren que este error que estoy viendo es en realidad un error conocido en el código de Apple ...

* INFORMACIÓN ACTUALIZADA *

He retrocedido y establecido puntos de interrupción en el código y estoy editando la pregunta original con esta información adicional. Cuando el usuario agrega un nuevo elemento a la base de datos, pasa de la vista raíz a la vista listCourses. La transacción de agregar funciona sin problemas y la lista de ViewView UITableView se actualiza perfectamente.

Cuando hago clic en la otra vista que también lee datos del mismo modelo de datos básicos, su controlador de vista ejecuta la siguiente secuencia pero nunca termina agregando el nuevo elemento a la vista de tabla. Aquí está la secuencia que atraviesa.

Simulador VC:

- controllerWillChangeContent which runs... [self.tableView beginUpdates]; - didChangeObject ..with message: NSFetchedResultsChangeUpdate ..which ran: [self configureCell:[tableView cellForRowAtIndexPath:indexPath] - controllerDidChangeContent: [self.tableView endUpdates];

El otro controlador de vista que funciona bien, pasa por esta secuencia inmediatamente después de que el registro se agrega a la base de datos.

ListCourses VC:

- didChangeSection ...with message: NSFetchedResultsChangeInsert ...which ran: [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; - didChangeObject ..with message: NSFetchedResultsChangeInsert ..which ran: [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];

¿Por qué un viewcontroller obtiene el mensaje NSFetchedResultsChangeInsert pero el otro no?

Aquí están los métodos delegados del controlador de vista defectuoso.

// 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 [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 } } - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { // The fetch controller is about to start sending change notifications, so prepare the table view for updates. NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__); [self.tableView beginUpdates]; } - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__); UITableView *tableView = self.tableView; switch(type) { case NSFetchedResultsChangeInsert: [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeUpdate: //[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath]; //[tableView reloadData]; break; case NSFetchedResultsChangeMove: [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; // Reloading the section inserts a new row and ensures that titles are updated appropriately. // [tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade]; break; } NSLog(@"vc>>> about to reload data"); // [self.tableView reloadData]; } - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__); switch(type) { case NSFetchedResultsChangeInsert: [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; } // [self.tableView reloadData]; } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { // The fetch controller has sent all current change notifications, so tell the table view to process all updates. NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__); [self.tableView endUpdates]; }

Gracias, phil


La comprobación de cordura UITableView funciona de la siguiente manera:

En la línea [self.tableView beginUpdates]; el tableView llama a su tableView: numberOfRowsInSection: método de delegado, que parece estar devolviendo 3. En la línea [self.tableView endUpdates]; lo llama nuevamente y parece estar devolviendo 4. Por lo tanto, tableView espera que inserte 1 fila entre estas dos líneas. De hecho, no se están insertando filas por lo que el tableView falla una aserción. (Puede ver el recuento de filas esperado y real en el mensaje de aserción).

El aumento de 3 filas a 4 filas muestra que NSFetchedResultsController está notando correctamente el elemento de Datos Core recién insertado. Lo que debe hacer es colocar un punto de interrupción al inicio de su controlador: método didChangeObject: atIndexPath: forChangeType: y recorrerlo cuando cambie a la segunda pestaña después de insertar un elemento. Debería ver el caso NSFetchedResultsChangeInsert: de la sentencia switch que se está ejecutando, pero obviamente esto no está sucediendo.

Con suerte, puede averiguar por qué no se está realizando la inserción; de lo contrario, vuelva y avísenos qué vio realmente al pasar por este método.

EDITADO PARA AGREGAR:

De acuerdo, los métodos de delegado de NSFetchedResultsController en el controlador de la 2da vista se invocan cuando se cambia a esa pestaña en lugar de inmediatamente cuando se inserta el nuevo elemento en la pestaña 1. Esto significa que el controlador de 2da vista no está viendo el inserto (lo cual debería suceder inmediatamente ) y en realidad está respondiendo a otra notificación de actualización de Datos principales más tarde que ocurre cuando se cambia a la pestaña 2. El controlador de resultados recuperados está trabajando con información obsoleta en la línea beginUpdates (en realidad hay 4 elementos en el conjunto de resultados aquí no 3). Para cuando llega a la línea endUpdates, ha actualizado su búsqueda y ha encontrado una inserción inesperada.

Los métodos delegados NSFetchedResultsController están realmente diseñados para actualizar la interfaz de usuario en el lugar mientras realiza cambios Y la vista del controlador está visible. En tu caso, estás haciendo cambios y LUEGO muestra el nuevo controlador de vista. El patrón que realmente debería utilizar es actualizar la vista de tabla en su método viewWillAppear del controlador 2. Algo así debería hacerlo:

- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; NSError *error = nil; [resultsController performFetch:&error]; // Refetch data if (error != nil) { // handle error } [self.tableView reloadData]; }

Esto asegurará que cada vez que cambie a la pestaña 2 esté trabajando con datos recientes del modelo.