TableView с двумя экземплярами NSFetchedResultsController

После нескольких дней исследований и перекодирования я практически в тупике. Моя цель - запустить тестовое приложение с одной таблицей, заполняемой двумя отдельными контроллерами fetchedResultControllers.

У меня есть серия товаров в списке покупок, каждый из которых имеет отдел и булев флаг "собран". Не собранные товары должны быть перечислены по отделам, затем следует один раздел, содержащий все собранные товары (независимо от отдела). По мере того как пользователь вычеркивает несобранные товары, они должны перемещаться вниз к разделу "собранные". Если пользователь снимает галочку с собранного предмета, он должен переместиться обратно в нужный отдел.

enter image description here

Для достижения первой части (несобранные предметы) я создал простой fetchedResultsController, который собирает все предметы, где collected = NO, и секционирует результаты по отделам:

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

Я установил количество строк, секций и заголовков секций следующим образом:

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

Я также использую шаблонные controllerWillChangeContent, didChangeObject и controllerDidChangeContent для добавления/удаления ячеек по мере изменения FRC.

Для краткости я не буду приводить код для отображения ячейки, но по сути я извлекаю нужный элемент на основе индексного пути ячейки, устанавливаю текст/подзаголовок ячейки и прикрепляю одно из двух изображений галочки, в зависимости от того, собран элемент или нет. Я также подключаю это изображение (которое находится на кнопке) для переключения с отмеченного на неотмеченный флажок при прикосновении к нему и соответствующим образом обновляю элемент.

Эта часть работает нормально. Я могу просматривать список элементов по отделам, и когда я помечаю один из них как собранный, я вижу, что он выпадает из списка, как и ожидалось.

Теперь я попытался добавить нижний раздел, содержащий все собранные предметы (в одном разделе). Сначала я настроил второй fetchedResultsConroller, на этот раз для получения только несобранных элементов и без секционирования. Мне также пришлось обновить следующее:

- (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";
    }
}

Затем я обновил cellForRowAtIndexPath аналогичным образом, так что элемент, который я получаю, приходит из правильного 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

При запуске все работает отлично. Таблица отображается точно так, как ожидалось:

enter image description here

Проблема (наконец-то)

Первая проблема возникает, когда я выбираю галочку для несобранного элемента. Я ожидаю, что предмет будет удален из отдела, в котором он числится, и перемещен в раздел "собранные". Вместо этого я получаю:

CoreData: ошибка: Серьезная ошибка приложения. Было поймано исключение от делегата NSFetchedResultsController во время вызова -controllerDidChangeContent:. Недопустимое обновление: недопустимое количество строк в секции 0. Количество строк, содержащихся в существующей секции после обновления (2) должно быть равно количеству строк, содержащихся в этом разделе до обновления (2), плюс или минус количество строк вставленных или удаленных из этого раздела (1 вставлено, 0 удалено) и плюс или минус количество строк, перемещенных в или из этой секции (0 перемещено in, 0 moved out). с userInfo (null)

Если я попытаюсь сделать наоборот, то получу другую ошибку:

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

Я подозреваю, что в обоих случаях есть проблема с согласованностью количества секций/строк в FRC и в представлении таблицы, когда элемент перемещается из одного FRC в другой. Хотя вторая ошибка заставляет меня думать, что, возможно, существует более простая проблема, связанная с извлечением элементов.

Любое направление или идеи были бы признательны. Я могу предоставить больше своего кода, если это поможет, а также создал небольшое тестовое приложение с одним видом для демонстрации проблемы. Я могу загрузить его, если это необходимо, но в основном я хотел проверить проблему в небольшой песочнице.

Обновление - запрошен дополнительный код

Как и просили, вот что происходит при касании галочки:

- (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");
    }
}
8
задан Ben Packard 25 January 2012 в 14:19
поделиться