8

Po wielu dniach badań i ponownego kodowania jestem prawie zaskoczony. Moim celem jest uruchomienie aplikacji testowej z pojedynczym widokiem tabeli z dwóch oddzielnych pobranych kontrolerów ResultController.TableView z dwoma instancjami NSFetchedResultsController

Mam szereg pozycji na liście zakupów, każda z działem i boolowską flagą "zebrane". Niepobrane przedmioty powinny być wymienione według działu, a następnie pojedyncza sekcja zawierająca wszystkie zebrane przedmioty (niezależnie od działu). Gdy użytkownik odpuści niepobrane pozycje, powinien przejść do sekcji "zebrane". Jeśli on/ona nie sprawdzi zebranego przedmiotu, powinien powrócić do właściwego działu.

enter image description here

Aby osiągnąć pierwsza część (nieodebrane przedmioty), skonfigurować prosty fetchedResultsController że pobiera wszystkie elementy gdzie zebrane = NIE, a skrawki wyniki według dziale:

- (NSFetchedResultsController *)firstFRC { 
    // Set up the fetched results controller if needed. 
    if (firstFRC == nil) { 

     // Create the fetch request for the entity. 
     NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 

     // fetch items 
     NSEntityDescription *entity = [NSEntityDescription entityForName:@"Item" inManagedObjectContext:managedObjectContext]; 
     [fetchRequest setEntity:entity]; 

     // only if uncollected 
     NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(collected = NO)"]; 
     [fetchRequest setPredicate:predicate]; 

     // sort by name 
     NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES]; 
     NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; 

     [fetchRequest setSortDescriptors:sortDescriptors]; 

     // fetch results, sectioned by department 
     NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@"department" cacheName:nil]; 
     aFetchedResultsController.delegate = self; 
     self.firstFRC = aFetchedResultsController; 
    } 

    return firstFRC; 
} 

ustawić liczba rzędów, sekcji i nagłówku sekcji, jak następuje:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 
{ 
    // Return the number of sections. 
    return firstFRC.sections.count; 
} 

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
{ 
    // Return the number of rows in the section. 
    return [[firstFRC.sections objectAtIndex:section] numberOfObjects]; 
} 

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { 
    return [[[firstFRC sections] objectAtIndex:section] name]; 
} 

również użyć controllerWillChangeContent szablonowe, didCha ngeObject i controllerDidChangeContent dodają/usuwają komórki jako zmiany FRC.

Dla zwięzłości, nie dołączę kodu do wyświetlania komórki, ale zasadniczo wyciągam właściwy element na podstawie ścieżki indeksu komórki, ustawiam tekst/podtytuł komórki i dołączam jeden z dwóch znaczników wyboru, w zależności od tego, czy przedmiot zostanie zebrany czy nie. Łączę też ten obraz (który znajduje się na przycisku), aby przełączyć z zaznaczonego na niezaznaczony po dotknięciu i odpowiednio zaktualizować przedmiot.

Ta część działa poprawnie. Mogę przeglądać listę przedmiotów według działu, a gdy zaznaczam jeden jako zebrany, widzę, jak znika z listy zgodnie z oczekiwaniami.

Teraz spróbowałem dodać dolną sekcję, zawierającą wszystkie zebrane przedmioty (w jednej sekcji). Najpierw ustawiłem drugi pobranyResultsConroller, tym razem, aby pobrać tylko niepobrane przedmioty i bez cięcia. Miałem też zaktualizować następujące elementy:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 
{ 
    // Return the number of sections - add one for 'collected' section 
    return firstFRC.sections.count + 1; 
} 

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
{ 
    // Return the number of rows in the section 
    if (section < firstFRC.sections.count) { 
     return [[firstFRC.sections objectAtIndex:section] numberOfObjects]; 
    } 
    else { 
     return secondFRC.fetchedObjects.count; 
    } 
} 

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { 
    if (section < firstFRC.sections.count) { 
     return [[[firstFRC sections] objectAtIndex:section] name]; 
    } 
    else{ 
     return @"Collected"; 
    } 
} 

Potem zaktualizowała cellForRowAtIndexPath w podobny sposób, tak, że element odzyskać pochodzi z prawej FRC:

Item *item; 
    if (indexPath.section < firstFRC.sections.count) { 
     item = [firstFRC objectAtIndexPath:indexPath]; 
    } 
    else { 
     item = [secondFRC objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]]; 
    } 

    [[cell textLabel] setText:[item name]]; 

…rest of cell configuration 

Działa to doskonale, kiedy uruchomić . Tableview wyświetla dokładnie zgodnie z oczekiwaniami:

enter image description here

Problem (wreszcie)

(pierwsze) Problem pojawia się kiedy wybrać znacznik dla nieodebranego towaru. Oczekuję, że przedmiot zostanie usunięty z działu, w którym jest wymieniony, i przeniesiony do sekcji "zebrane".Zamiast tego otrzymujemy:

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 (2) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (1 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)

Jeśli próbuję coś przeciwnego, to otrzymuję inny błąd:

* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayM objectAtIndex:]: index 2 beyond bounds [0 .. 1]'

Podejrzewam, że w obu przypadkach pojawia się problem ze konsystencji z liczbą odcinków/wiersze we FRC i tableview, gdy element przenosi się z jednego FRC na drugi. Chociaż ten drugi błąd sprawia, że ​​myślę, że może jest prostszy problem związany z moim odzyskiwaniem przedmiotów.

Każdy kierunek lub pomysły będą mile widziane. Mogę dostarczyć więcej kodu, jeśli mogłoby to pomóc, a także stworzyłem małą aplikację testową z pojedynczym widokiem, aby zademonstrować problem. Mogę go przesłać w razie potrzeby, ale przede wszystkim chciałem przetestować ten problem w małej piaskownicy.

Update - kod dodatkowy wniosek

Zgodnie z wnioskiem, to co się dzieje, gdy zaznaczenie dotknął:

- (void)checkButtonTapped:(id)sender event:(id)event 
{ 
    NSSet *touches = [event allTouches]; 
    UITouch *touch = [touches anyObject]; 
    CGPoint currentTouchPosition = [touch locationInView:self.tableView]; 
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition]; 
    if (indexPath != nil) 
    { 
     [self tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath]; 
    } 
} 

- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath 
{ 
    Item *item; 
    if (indexPath.section < firstFRC.sections.count) { 
     item = [firstFRC objectAtIndexPath:indexPath]; 
    } 
    else { 
     item = [secondFRC objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]]; 
    } 

    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; 
    UIButton *button = (UIButton *)cell.accessoryView; 

    if (![item collected]) { 
     [item setCollected:YES];   
     [button setBackgroundImage:[UIImage imageNamed:@"checked.png"] forState:UIControlStateNormal]; 
    } 
    else if ([item collected]){ 
     [item setCollected:NO]; 
     [button setBackgroundImage:[UIImage imageNamed:@"unchecked.png"] forState:UIControlStateNormal]; 
    } 
    NSError *error = nil; 
    if (![item.managedObjectContext save:&error]) { 
     NSLog(@"Error saving collected items"); 
    } 
} 
+0

Uwaga: nazwy działów są nieznacznie wyłączone na zrzucie ekranu. Zapomniałem użyć dept jako pierwszego deskryptora sortowania. –

+0

W jaki sposób ponownie ładujesz swój stół? –

+0

Opublikuj kod, który zostanie wywołany po dotknięciu znacznika wyboru. – jcm

Odpowiedz

6

Cóż, myślę, że mogłem go złamać. Jak to często bywa, pozbycie się go i przeczytanie kilku uwag wskazało mi właściwy kierunek.

Po przejściu do metody delegata controllerDidChangeObject, próbuję wstawić wiersz w podanej pozycji indexPath (dla elementu "zaznaczonego"). Z wyjątkiem tego, że wstawiając do mojej dodatkowej sekcji, ta indexPath nie ma świadomości, że przed nią jest kilka innych sekcji. Otrzymuje więc sekcję 0 i próbuje tam wstawić. Zamiast tego, jeśli indexPath pochodzi z drugiego FRC, powinienem zwiększać numer sekcji o liczbę sekcji w pierwszej tabeli FRC. Więc wymieniłem:

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

UITableView *tableView = self.tableView; 

    switch(type) { 
     case NSFetchedResultsChangeInsert: 
       [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 

     break; 

z

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

UITableView *tableView = self.tableView; 

switch(type) { 
    case NSFetchedResultsChangeInsert: 

     if ([controller.sectionNameKeyPath isEqualToString:@"department"]) { 
      [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 
     } 
     else { 
      [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:newIndexPath.row inSection:firstFRC.sections.count]] withRowAnimation:UITableViewRowAnimationFade];   } 
     break; 

będę musiał to zrobić dla każdego z wkładką, usuwać, aktualizować itp będę oznaczyć to jako odpowiedź po tym, jak potwierdziły to i do dać czas na inne komentarze.

+0

Potwierdzono działanie po tej zmianie. –

+0

Można również sprawdzić, czy ([controller isEqualTo: self.firstFetchedResultsController]) – 3lvis

0

błędy widzisz myśli, która sama dba twoi UITableView ładuje zarówno przed swoją NSFetchedResultsController do zrobienia. Kody, które wysłałeś, są prawdopodobnie poprawne. Podejrzeń, że problem jest w jednym z metod NSFetchedResultsControllerDelegate.

+0

Masz na myśli kontrolerWillChangeContent, itp? Te rozdarłem hurtowo z demo Apple. –

+0

Podejrzewałeś poprawnie, wielkie dzięki! –