Поведение NSFetchedResultsControllerDelegate 'ChangeUpdate' нарушено?

Документы для NSFetchedResultsControllerDelegate предоставляют следующий пример кода.

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

        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];
            [tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
            break;

    }

}

Когда я создаю новый NSManagedObject, срабатывает NSFetchedResultsChangeInsert (отлично!). Когда я изменяю значение атрибута (используемого для заголовка ячейки), срабатывает NSFetchedResultsChangeUpdate. К сожалению, новый заголовок не отображается автоматически, если я не перезагружу таблицу, раздел или строку. Действительно, если новое имя приводит к другой сортировке набора результатов, то срабатывает NSFetchedResultsChangeMove, и все в порядке, поскольку предоставленный код перезагружает весь раздел.

UITableView имеет метод reloadRowsAtIndexPaths: withRowAnimation, поэтому я попытался использовать его в блоке кода NSFetchedResultsChangeUpdate. Это действительно работает ... но документы для этого конкретного метода читаются так, как будто мне это не нужно (обратите внимание на последнюю строку):

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

И да, если я записываю, что происходит, когда

[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath]; 

вызывается в NSFetchedResultsChangeUpdate, он может получить последнее значение «name» и установить его в textLabel ячейки. Имя просто не отображается в ячейке, если я не перезагружу его. Даже если я просто щелкну по ячейке, имя появится. Обратите внимание, что для воссоздания этого поведения необходимо создать новый управляемый объект и затем дать ему имя, которое заставит его сортировать FIRST в NSFetchedResultsController. Таким образом, NSFetchedResultsChangeMove не срабатывает (что действительно работает, поскольку перезагружает раздел).

Я что-то упустил или это ожидаемое поведение? «Обсуждение» reloadRowsAtIndexPaths наводит меня на мысль, что я должен иметь возможность просто установить textLabel ячейки без перезагрузки строки, раздела или таблицы.


person Luther Baker    schedule 25.08.2009    source источник
comment
Это должно работать. Я делаю то же самое. При изменении атрибутов при вызове save: уведомления MOC гаснут. NSFetchedResultsController видит изменение и вызывает мой метод configureCell: atIndexPath. Там я беру значения и заполняю ячейку, и ячейка немедленно обновляется. Опубликуйте свой код configureCell.   -  person Steve Weller    schedule 21.11.2009


Ответы (4)


Вы должны вызвать [cell setNeedsLayout] или / и [cell setNeedsDisplay], чтобы вызвать обновление ячейки, в зависимости от реализации вашей ячейки.

Если вы составляете ячейку подвидов, как мы обычно, вы полагаетесь на - layoutSubviews, поэтому вам следует вызвать [cell setNeedsLayout].

Если вы рисуете ячейку напрямую с помощью – drawRect:, вы должны вызвать [cell setNeedsDisplay].

Если вы используете и композицию, и рисунок, вы должны вызвать и то, и другое.

person an0    schedule 13.05.2011

Хотя это правда, что вам не нужно перезагружать ячейку, чтобы изменения вступили в силу, вы должны помнить, что iPhone кэширует чертеж вида в максимально возможной степени. После того, как вы заново настроили свою ячейку, вам нужно вызвать setNeedsDisplay в ячейке, чтобы запустить перерисовку.

person Douglas Mayle    schedule 12.10.2009
comment
На этом этапе - как лучше всего получить ячейку, которая находится на экране? tableview: cellForRowAtIndexPath: создает новую ячейку, тогда как я хочу получить ссылку на ячейку на экране ... верно? - person Luther Baker; 15.10.2009
comment
Обычно в настраиваемой ячейке нет необходимости вызывать setNeedsDisplay. Если это новая ячейка, у нее нет кеша, поэтому не беспокойтесь, если вы меняете конфигурацию, она будет перерисована. Однако, если вы действительно хотите знать, какие экраны видны, взгляните на мой ответ на: stackoverflow.com/questions/996515/ - person Douglas Mayle; 15.10.2009

Хотя это не было явно указано, вам действительно нужно убедиться, что вы включили методы делегата для controllerWillChangeContent: и controllerDidChangeContent:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView beginUpdates];
}

и

- (void)controllerDidChangeContent:(BSFetchedResultsController *)controller {
    @try {
        [self.tableView endUpdates];
    }
    @catch (NSException * e) {
        NSLog(@"caught exception: %@: %@", [e name], [e description]);
    }
    @finally { }
}

NSFRC может запускать несколько методов контроллера: didChangeObject: atIndexPath: forChangeType: newIndexPath: во время любого одного изменения, поэтому не ожидайте, что строка таблицы будет обновляться сразу после каждого из них. Они обновятся только после того, как вы вызовете endUpdates в таблице.

person Daniel Thorpe    schedule 09.02.2011

Вызов логики обновления пользовательского интерфейса в MAIN Thread решил мою проблему (оба [cell setNeedsLayout] и [cell setNeedsDisplay] не работают для меня):

...
case NSFetchedResultsChangeUpdate:
    dispatch_async(dispatch_get_main_queue(), {
      [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
    });
    break;
...

Более того (не об этой проблеме, но будет полезно), вам лучше использовать newIndexPath, если он доступен:

...
case NSFetchedResultsChangeUpdate:
    dispatch_async(dispatch_get_main_queue(), {
      NSIndexPath * targetIndexPath = (newIndexPath ?: indexPath)
      [self configureCell:[tableView cellForRowAtIndexPath:targetIndexPath]
              atIndexPath:targetIndexPath];
    });
    break;
...
person Kjuly    schedule 21.04.2015
comment
эти события уже находятся в основном потоке, то, что вы делаете, - это переход к следующему циклу событий, хотя GCD не подходит для этого на 100%, выполнение после задержки более надежно. - person malhal; 01.09.2015