1

Ich habe einen UITableViewController mit einem UITableView implementiert, angetrieben von den Ergebnissen von zwei NSFetchedResultsControllers (eine Tabelle mit zwei Abschnitten, ein Abschnitt für jeden NSFetchedResultsControllers), wie zuvor discussed here.UITableView von zwei NSFetchedResultsControllern mit gemeinsamen Ergebnissen

Mein Code läuft korrekt und ohne Fehler, wenn beide NSFetchedResultsControllers irgendwelche Ergebnisse nicht gemeinsam teilen.

aber ich laufen in dieses Problem, wenn ich ein Core Data-Objekt einfügen, die in beiden meiner NSFetchedResultsControllers als Ergebnis zeigt sich:

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) 

ich Ausgeben bin, wenn ich für das gefragt werde, Anzahl der Zeilen in einem Schnitt:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 
    id <NSFetchedResultsSectionInfo> sectionInfo; 
    if (section < [self.fetchedResultsController1.sections count]) { 
     sectionInfo = [[self.fetchedResultsController1 sections] objectAtIndex:section]; 
    } 
    else { 
     sectionInfo = [[self.fetchedResultsController2 sections] objectAtIndex:section-[self.fetchedResultsController1.sections count]]; 
    } 

    NSLog(@"Section %d with %d row(s)", section, [sectionInfo numberOfObjects]); 
    return [sectionInfo numberOfObjects]; 
} 

und auszudrucken, wenn ein Objekt in didObject eingefügt wird:

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { 

    UITableView *tableView = self.tableView; 

    switch(type) { 

     case NSFetchedResultsChangeInsert: 
      NSLog(@"Inserting %@ %@", newIndexPath, anObject); 
      [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]; 
      // Reloading the section inserts a new row and ensures that titles are updated appropriately. 
      [tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade]; 
      break; 
    } 
} 

Wenn ich mit dem obigen Code ausführen, bekomme ich die folgende Ausgabe (der Klarheit halber abgekürzt), mit dem obigen Fehler sofort diese folgen:

2012-03-28 00:59:55.611 MyApp[517:207] Section 1 with 0 row(s) 
2012-03-28 00:59:55.612 MyApp[517:207] Section 0 with 0 row(s) 
2012-03-28 00:59:55.613 MyApp[517:207] Inserting <NSIndexPath 0x5948150> 2 indexes [0, 0] <MyClass: 0xd60f8b0> (entity: MyClass; id: 0xd611d50 <x-coredata:///MyClass/tD7E0DB3C-4D85-468C-9DD0-BAD0A53B20A310> ; data: { 
    .... 
}) 
2012-03-28 00:59:55.614 MyApp[517:207] Section 0 with 1 row(s) 
2012-03-28 00:59:55.614 MyApp[517:207] Section 1 with 0 row(s) 
2012-03-28 00:59:55.618 MyApp[517:207] Inserting <NSIndexPath 0x5948150> 2 indexes [0, 0] <MyClass: 0xd60f8b0> (entity: MyClass; id: 0xd611d50 <x-coredata:///MyClass/tD7E0DB3C-4D85-468C-9DD0-BAD0A53B20A310> ; data: { 
    .... 
}) 
2012-03-28 00:59:55.619 MyApp[517:207] Section 0 with 1 row(s) 
2012-03-28 00:59:55.620 MyApp[517:207] Section 1 with 1 row(s) 
2012-03-28 00:59:55.620 MyApp[517:207] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1448.89/UITableView.m:995 
2012-03-28 00:59:55.621 MyApp[517: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) 

zwei NSFetchedResultsControllers Ist aufweist, kann eine überlappen kein einziges UITableView deren Ergebnisse Fahr gute Idee? Das ist eine akzeptable Antwort, aber wenn dies möglich sein sollte, was kann ich tun, um diesen Fehler zu vermeiden?

Antwort

1

Ich gehe davon aus, dass Sie die Änderungen wickeln, die in controller:didChangeObject:... in [tableView beginUpdates] und [tableView endUpdates] Anrufen in den entsprechenden NSFetchedResultsControllerDelegate Methoden geschehen.

So ruft jede NSFRC beginUpdates, insertCells und endUpdates. In dieser Reihenfolge.

Wahrscheinlich ist das Problem, dass die TabelleView die Anzahl der Abschnitte und die Anzahl der Zeilen für jeden Abschnitt nach dem ersten Aufruf an endUpdates anfordert. An diesem Punkt in einem Abschnitt stimmt die Anzahl der Zellen mit dem Ergebnis von tableView:numberOfRowsInSection: überein. Leider stimmt es in dem anderen Abschnitt nicht überein, weil der zweite geholteResultsController seine Delegate-Methoden nicht ausgeführt hat. Die zweite NSFRC konnte also noch keine neuen Zellen einfügen, aber sie gibt bereits ihre neue Anzahl von Objekten zurück.

So wie diese zu lösen. Ich kann mir drei verschiedene Wege vorstellen. Der erste ist der beste, der letzte sollte überhaupt nicht gemacht werden.

Option 1: Schreiben Sie Ihr Datenmodell so um, dass ein NSFetchedResultsController für Sie arbeitet. Legen Sie einfach das Attribut fest, das Sie verwenden, um zu entscheiden, in welchem ​​Ihrer 2 NSFRCs ein Objekt in das Datenmodell gehört, und legen Sie einen entsprechenden AbschnittKeyPath fest. Wahrscheinlich benötigen Sie einen dritten Abschnitt für Objekte, die zu beiden Abschnitten gehören, da ein Objekt nicht gleichzeitig in zwei Abschnitten sein kann.

Option 2: die NSFetchedResultsController Dump und NSArrays verwenden. Wenn Kerndatenänderungen von einem anderen Ort aus stattfinden können (z. B. ein Import, der im Hintergrund ausgeführt wird), wird es Ihnen schwer fallen, da Sie diese Änderungen selbst verfolgen müssen. Sie müssen Benachrichtigungen senden, wenn ein anderer Prozess Core-Datenobjekte ändert. Sie müssen die Änderungen, die auch von Ihrem ViewController passieren, verwalten. Sie könnten eine Kopie des alten Arrays erstellen, ein neues Array abrufen, die Arrays vergleichen und gegebenenfalls Zellen einfügen oder löschen.

Option 3: Wenn Sie schlechten Code und schmutzige Tricks mögen, könnten Sie versuchen, etwas wie diesen Pseudocode zu machen.

  • in controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:

    if (change will cause insert or delete in both sections) { 
    if (!delayEndUpdates) { 
        self.delayEndUpdates = YES 
        self.firstUpdatingController = controller; 
        [tableView beginUpdates]; // another beginUpdates should stop the tableView from updating. At least in theory 
    } 
    } 
    else { 
    self.delayEndUpdates = NO; 
    } 
    
  • in controllerDidChangeContent:

    [tableView endUpdates]; // the original 
    if (self.firstUpdatingController != controller) { 
    // after the second NSFRC did change its content 
    if (delayEndUpdates) { 
        [tableView endUpdates]; // another one to start tableView updating again 
    } 
    self.firstUpdatingController = nil; 
    } 
    

die Idee ist, alle Änderungen zu wickeln, die Einsätze oder Deletionen in beiden Abschnitten in einem anderen Satz von beginUpdates verursachen und EndUpdates. Ich bin mir jedoch nicht sicher, ob das funktioniert. Die Dokumentation besagt, dass Sie beginUpdates und endUpdates verschachteln können, aber ich habe es noch nie versucht.

+0

Sie sind richtig, meine 'NSFetchedResultsControllerDelegate' Methoden rufen' [tableView beginUpdates] 'und' [tableView endUpdates] 'auf, und das gilt auch: tableView fordert die Anzahl der Abschnitte und die Anzahl der Zeilen für jeden Abschnitt nach dem an erster Aufruf von 'endUpdates'. Ich werde eine modifizierte Version Ihres ersten Ansatzes verfolgen. Ich denke, ich brauche noch zwei 'NSFetchedResultController's, da ich in meinen Sektionen zwei verschiedene Sortierreihenfolgen benötige, aber ich kann vermeiden, das gleiche Objekt in zwei verschiedenen Sektionen zu haben. –