8

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

У меня есть серия предметов в списке покупок, каждый из которых имеет отдел и булевский «собранный» флаг. Отобранные предметы должны быть перечислены отделом, а затем один раздел, содержащий все собранные предметы (независимо от отдела). Когда пользователь проверяет несохраненные элементы, они должны перейти в раздел «собрано». Если он/она проверяет собранный предмет, он должен вернуться в свой правильный отдел.

enter image description here

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

- (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, didCha ngeObject и 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 

Это прекрасно работает, когда я запускаю , В Tableview показывает точно, как ожидалось:

enter image description here

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

The (первая) Проблема возникает, когда я выбираю для галочки в неполученной пункта. Я ожидаю, что элемент будет удален из отдела, который указан в списке, и перешел в раздел «собрано».Вместо этого, я получаю:

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)

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

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

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

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

Update - дополнительный код просил

В соответствии с просьбой, это то, что происходит, когда галочка прикосновении:

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

Примечание: названия отделов на скриншоте слегка отшлифованы. Я забыл использовать депт как первый дескриптор сортировки. –

+0

Как вы перезагружаете свой стол? –

+0

Отправьте код, который вызывается при нажатии галочки. – jcm

ответ

6

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

Когда я перехожу к методу делегата controllerDidChangeObject, я пытаюсь вставить строку в указанную indexPath (для элемента «checked»). За исключением того, что при вставке в мой дополнительный раздел этот indexPath не осознает тот факт, что перед ним есть куча других разделов. Поэтому он получает раздел 0 и пытается его вставить. Вместо этого, если indexPath поступает из второго FRC, я должен увеличивать номер раздела на количество секций в первой таблице FRC. Итак, я заменил:

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

с

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

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

+0

Подтверждена работа после этого изменения. –

+0

Вы также можете проверить ((controller isEqualTo: self.firstFetchedResultsController]) – 3lvis

0

Ошибки, которые вы видите в виду, что ваш сам UITableView перезагружается перед тем как ваш NSFetchedResultsController с. Коды, которые вы опубликовали, вероятно, верны. Я подозреваю, что проблема заключается в одном из методов NSFetchedResultsControllerDelegate.

+0

Вы имеете в виду controllerWillChangeContent и т. Д.? Эти я сорвал оптовую продажу с демонстрации Apple. –

+0

Вы подозревали правильно, большое спасибо! –