11

La mia applicazione ha due barre di tabulazione ... Ciascuno porta l'utente a un tableviewcontroller che gli presenta un elenco di elementi. La prima vista consente all'utente di registrare le voci nel database. L'altra scheda/vista legge dal database e presenta tali elementi all'utente, tuttavia, non vengono apportati aggiornamenti all'archivio core/persistente da questa seconda vista.Errore CoreData che mi fa impazzire ... CoreData: Grave errore dell'applicazione. Un'eccezione catturata dal delegato di NSFetchedResultsController

Quando aggiungo un nuovo elemento tramite il primo viewcontroller, si presenta perfettamente nella vista. Tuttavia, non appena tocco l'altra barra delle schede per vedere il nuovo elemento visualizzato in quel viewcontroller, ottengo l'errore elencato di seguito, e l'elemento appena aggiunto non viene visualizzato ... Nota: se interrompo l'app e ricarico/ri-eseguilo e inizia toccando la seconda barra delle schede, il nuovo elemento si presenta bene, quindi so che il modello è stato aggiornato correttamente.

*** 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) 

Codice dall'applicazione delegato in cui il managedObjectContext viene passato ai due 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]; 

} 

Qualsiasi aiuto è possibile fornire sarebbe molto apprezzato.

Ho cercato questo sito e ho trovato molte istanze di questo errore, ma nessuno sembra adattarsi. Ho visto anche i riferimenti dedurre che questo errore che sto vedendo è in realtà un bug noto nel codice di Apple ...

* Informazioni aggiornate *

sono tornato e impostare i punti di interruzione in il codice e sto modificando la domanda originale con queste informazioni aggiuntive. Quando l'utente aggiunge un nuovo elemento al database, viene trasferito da rootview alla vista listCourses. La transazione di aggiunta funziona perfettamente e la visualizzazione listCourses di UITableView viene aggiornata perfettamente.

Quando faccio clic sull'altra vista che legge anche i dati dallo stesso modello di dati di base, è viewcontroller che esegue la seguente sequenza ma non termina mai di aggiungere il nuovo elemento a tableview. Ecco la sequenza che attraversa.

Simulator VC:

- controllerWillChangeContent which runs... 
     [self.tableView beginUpdates]; 

    - didChangeObject 
     ..with message: NSFetchedResultsChangeUpdate 
     ..which ran: 
     [self configureCell:[tableView cellForRowAtIndexPath:indexPath] 

    - controllerDidChangeContent: 
     [self.tableView endUpdates]; 

L'altra viewcontroller che funziona grande, passa attraverso questa sequenza immediatamente dopo il record viene aggiunto al database.

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]; 

Perché quello viewcontroller ottenere il messaggio NSFetchedResultsChangeInsert ma l'altro non lo fa?

Ecco i metodi delegati dal controller di vista guasto.

// 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]; 

} 

Grazie, phil

risposta

20

La sanità mentale UITableView controllo funziona così:

Nella riga [self.tableView beginUpdates]; la tabellaView chiama la tua tabellaView: numberOfRowsInSection: metodo delegate, che sembra restituire 3. Alla riga [self.tableView endUpdates]; lo chiama di nuovo e sembra che stia tornando 4.Pertanto, tableView si aspetta che tu inserisca 1 riga tra queste due linee. Di fatto, nessuna riga viene inserita in modo che tableView fallisca un'asserzione. (È possibile visualizzare i conteggi delle righe previsti e effettivi nel messaggio di asserzione).

L'aumento da 3 righe a 4 righe indica che NSFetchedResultsController sta rilevando correttamente l'elemento Dati principali appena inserito. Quello che devi fare è mettere un breakpoint all'inizio del tuo controller: didChangeObject: atIndexPath: forChangeType: metodo e passarci attraverso quando passi alla seconda scheda dopo aver inserito un elemento. Dovresti vedere NSFetchedResultsChangeInsert: caso dell'istruzione switch che viene eseguita, ma ovviamente non sta succedendo.

Speriamo che tu possa capire perché l'inserimento non sta avvenendo, altrimenti torna indietro e facci sapere che cosa hai effettivamente visto quando passi attraverso questo metodo.

A cura di aggiungere:

OK, in modo che i metodi delegato NSFetchedResultsController nel 2 ° controller della vista vengono chiamati quando si passa a quella scheda piuttosto che immediatamente quando il nuovo elemento viene inserito nella scheda 1. Ciò significa che il 2 ° il controller vista non vede l'inserto (che dovrebbe accadere immediatamente) e risponde in realtà ad altre notifiche di aggiornamento di Core Data successive che si verificano quando si passa alla scheda 2. Il controller risultati recuperato sta lavorando con informazioni obsolete sulla riga beginUpdates (ci sono in realtà 4 elementi nel risultato impostato qui non 3). Nel momento in cui arriva alla riga endUpdates, ha aggiornato il recupero e trovato un inserto imprevisto.

I metodi delegati NSFetchedResultsController sono davvero progettati per aggiornare l'interfaccia utente sul posto mentre si apportano modifiche E la vista del controller è visibile. Nel tuo caso, stai apportando delle modifiche e POI si sta visualizzando il nuovo controller di visualizzazione. Il modello che davvero dovrebbe utilizzare è quello di aggiornare la Tableview nel metodo viewWillAppear del controllore 2. Qualcosa del genere dovrebbe farlo:

- (void)viewWillAppear:(BOOL)animated 
{ 
    [super viewWillAppear:animated]; 

    NSError *error = nil; 
    [resultsController performFetch:&error]; // Refetch data 
    if (error != nil) { 
     // handle error 
    } 

    [self.tableView reloadData]; 
} 

Questo farà in modo che ogni volta che si passa alla scheda 2 si sta lavorando con nuovi dati dal modello.

+1

Grazie, Robin! Ti seguirò e ti farò sapere la mattina ... 1am qui! – phil

+0

Tutti, fare riferimento alle informazioni aggiornate alla fine della domanda originale. Ho fornito i metodi del delegato tableview e il flusso di chiamate che sto vedendo attraverso i punti di interruzione e il debugger. Qualche idea per cui i miei metodi di delega della vista tabella vcSimulator non ricevono mai il messaggio NSFetchedResultsChangeInsert? – phil

+0

Risposta modificata sopra. –

Problemi correlati