11

Moja aplikacja ma dwa paski zakładek ... Każdy zabiera użytkownika do tableviewcontroller, który przedstawia mu listę elementów. Pierwszy widok pozwala użytkownikowi rejestrować wpisy w bazie danych. Druga karta/widok czyta z bazy danych i przedstawia te elementy również użytkownikowi, jednak z tego drugiego widoku nie są dokonywane żadne aktualizacje do magazynu coreData/persistant.Błąd CoreData doprowadza mnie do szaleństwa ... CoreData: Poważny błąd aplikacji. Wyjątek przechwycony przez delegata NSFetchedResultsController

Po dodaniu nowego elementu za pomocą pierwszego kontrolera view, pojawia się on idealnie w widoku. Jednak, gdy tylko dotknę drugiego paska kart, aby zobaczyć nowy element pojawia się w tym kontrolerem view, pojawia się błąd wymieniony poniżej, a nowo dodany element nie pojawia się ... Uwaga: jeśli zatrzymam aplikację i przeładuję/uruchom go ponownie i zacznij od dotknięcia drugiego paska kart, nowy element pokazuje się dobrze, więc wiem, że model jest aktualizowany poprawnie.

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

Kod z aplikacji pełnomocnika, w której obiekt managedObjectContext jest przekazywany do dwóch kontrolerów viewController.

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

} 

Każda pomoc, jakiej możesz udzielić, byłaby bardzo doceniana.

Przeszukałem tę witrynę i znalazłem wiele przypadków tego błędu, ale żaden nie wydaje się odpowiedni. Ja również widziałem odniesień wywodząc, że to błąd widzę jest rzeczywiście znany błąd w kodzie jabłko ...

* Aktualizacja informacji *

Poszedłem z powrotem i określone punkty przerwy w kod i edytuję oryginalne pytanie z tymi dodatkowymi informacjami. Gdy użytkownik doda nowy element do bazy danych, zostaje przeniesiony z widoku root do widoku listCourses. Dodana transakcja działa bezbłędnie, a UITableView listCourses View jest perfekcyjnie aktualizowany.

Kiedy klikam na inny widok, który również odczytuje dane z tego samego podstawowego modelu danych, jego kontroler view przebiega przez następującą sekwencję, ale nigdy nie kończy dodawania nowego elementu do widoku tabeli. Oto sekwencja, przez którą przechodzi.

Simulator VC:

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

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

    - controllerDidChangeContent: 
     [self.tableView endUpdates]; 

Drugi viewcontroller że działa świetnie, przechodzi natychmiast po tej sekwencji rekord jest dodawany do bazy danych.

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

Dlaczego jeden viewcontroller dostać komunikat NSFetchedResultsChangeInsert ale druga nie?

Oto metody delegatów z wadliwego kontrolera viewcontroller.

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

} 

Dzięki Phil

Odpowiedz

20

UITableView poczytalność sprawdzanie działa tak:

Na linii [self.tableView beginUpdates]; tableView wywołuje twoja tableView: numberOfRowsInSection: deleguj metodę, która wydaje się zwracać 3. W linii [self.tableView endUpdates]; ponownie go wzywa i wydaje się, że wraca 4.Dlatego tableView oczekuje wstawienia 1 wiersza między tymi dwoma wierszami. W rzeczywistości żadne wiersze nie są wstawiane, więc tabelaView kończy się niepowodzeniem asercji. (Możesz zobaczyć oczekiwane i rzeczywiste liczby wierszy w wiadomości potwierdzenia).

Wzrost z 3 wierszy do 4 wierszy pokazuje, że NSFetchedResultsController poprawnie zauważa nowo wstawiony element danych podstawowych. Co musisz zrobić, to umieścić punkt przerwania na początku kontrolera: didChangeObject: atIndexPath: forChangeType: metoda i przejdź przez nią po przełączeniu na drugą kartę po wstawieniu elementu. Powinieneś zobaczyć NSFetchedResultsChangeInsert: case instrukcji switch, która jest wykonywana, ale oczywiście tak się nie dzieje.

Mamy nadzieję, że możesz dowiedzieć się, dlaczego wstawienie się nie odbywa - w przeciwnym razie wróć i daj nam znać, co faktycznie zobaczyłeś, przechodząc przez tę metodę.

EDITED dodać:

OK, więc metody delegata NSFetchedResultsController w 2. kontrolera widoku nazywane są po przełączeniu na tej karcie, a nie natychmiast, gdy nowy element jest wstawiany na karcie 1. Oznacza to, że 2 kontroler widoku nie widzi wkładki (co powinno nastąpić natychmiast) i faktycznie odpowiada na inne powiadomienie o aktualizacji danych podstawowych później, które pojawia się po przejściu do zakładki 2. Kontroler pobranych wyników pracuje z nieaktualnymi informacjami na linii beginUpdates (są właściwie 4 pozycje w zestawie wyników nie 3). Zanim dotrze do linii endUpdates, odświeżyło pobieranie i znalazło nieoczekiwaną wstawkę.

Metody delegatów NSFetchedResultsController są rzeczywiście zaprojektowane do aktualizowania interfejsu użytkownika w miejscu podczas wprowadzania zmian ORAZ widoku kontrolera jest widoczny. W twoim przypadku dokonujesz zmian i NASTĘPNIE wyświetlasz nowy kontroler widoku. Wzór, który naprawdę powinien być używany jest do odświeżenia tableview w swojej metodzie viewWillAppear kontrolera 2. Coś jak to powinno zrobić:

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

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

    [self.tableView reloadData]; 
} 

To będzie upewnić się, że po przełączeniu na kartę 2 to jest praca z świeże dane z modelu.

+1

Dziękuję, Robin! Pójdę za tobą i dam ci znać rano ... 1 nad ranem! – phil

+0

Wszystko, proszę zapoznać się z aktualnymi informacjami na końcu oryginalnego pytania. Dostarczyłem metod delegatów tableview i przepływ połączeń, które widzę za pomocą punktów przerwania i debuggera. Jakieś pomysły, dlaczego moje metody delegowania tabel vcSimulator nigdy nie otrzymują komunikatu NSFetchedResultsChangeInsert? – phil

+0

Edytowana odpowiedź powyżej. –

Powiązane problemy