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.
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:
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");
}
}
Uwaga: nazwy działów są nieznacznie wyłączone na zrzucie ekranu. Zapomniałem użyć dept jako pierwszego deskryptora sortowania. –
W jaki sposób ponownie ładujesz swój stół? –
Opublikuj kod, który zostanie wywołany po dotknięciu znacznika wyboru. – jcm