iOS 9 - попытаться удалить и перезагрузить тот же путь индекса

Это ошибка:

CoreData: ошибка: серьезная ошибка приложения. Было обнаружено исключение от делегата NSFetchedResultsController во время вызова -controllerDidChangeContent :. попытаться удалить и перезагрузить тот же индексный путь ({length = 2, path = 0 - 0}) с помощью userInfo (null)

Это мой типичный NSFetchedResultsControllerDelegate:

func controllerWillChangeContent(controller: NSFetchedResultsController) {
    tableView.beginUpdates()
}

func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {

    let indexSet = NSIndexSet(index: sectionIndex)

    switch type {
    case .Insert:
        tableView.insertSections(indexSet, withRowAnimation: .Fade)
    case .Delete:
        tableView.deleteSections(indexSet, withRowAnimation: .Fade)
    case .Update:
        fallthrough
    case .Move:
        tableView.reloadSections(indexSet, withRowAnimation: .Fade)
    }
}

func controller(controller: NSFetchedResultsController, didChangeObject anObject: NSManagedObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {

    switch type {
    case .Insert:
        if let newIndexPath = newIndexPath {
            tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
        }
    case .Delete:
        if let indexPath = indexPath {
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        }
    case .Update:
        if let indexPath = indexPath {
            tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
        }
    case .Move:
        if let indexPath = indexPath {
            if let newIndexPath = newIndexPath {
                tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
                tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
            }
        }
    }
}

func controllerDidChangeContent(controller: NSFetchedResultsController) {
    tableView.endUpdates()
}

in viewDidLoad():

private func setupOnceFetchedResultsController() {

    if fetchedResultsController == nil {
        let context = NSManagedObjectContext.MR_defaultContext()
        let fetchReguest = NSFetchRequest(entityName: "DBOrder")
        let dateDescriptor = NSSortDescriptor(key: "date", ascending: false)

        fetchReguest.predicate = NSPredicate(format: "user.identifier = %@", DBAppSettings.currentUser!.identifier )
        fetchReguest.sortDescriptors = [dateDescriptor]
        fetchReguest.fetchLimit = 10
        fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchReguest, managedObjectContext: context, sectionNameKeyPath: "identifier", cacheName: nil)
        fetchedResultsController.delegate = self

        try! fetchedResultsController.performFetch()
    }
}

person Bartłomiej Semańczyk    schedule 13.07.2015    source источник


Ответы (6)


Это похоже на ошибку в iOS 9 (которая все еще находится в стадии бета-тестирования) и также обсуждается на форуме разработчиков Apple.

Я могу подтвердить проблему с симулятором iOS 9 из Xcode 7 beta 3. Я заметил, что для обновленного управляемого объекта метод делегата didChangeObject: вызывается дважды: один раз с событием NSFetchedResultsChangeUpdate, а затем снова с NSFetchedResultsChangeMove событие (и indexPath == newIndexPath).

Добавление явной проверки для indexPath != newIndexPath, как предложено в приведенном выше потоке, похоже, решает проблему:

        case .Move:
            if indexPath != newIndexPath {
                tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
                tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
        }
person Martin R    schedule 13.07.2015
comment
Это решило мою проблему. Но когда я добавляю несколько строк, возникает другая проблема: attempt to delete row 0 from section 12, but there are only 10 sections before the update with userInfo (null). Как вы думаете, это тоже ошибка? - person Bartłomiej Semańczyk; 13.07.2015
comment
@ BartłomiejSemańczyk: Это может быть ошибка в вашем коде. Эта проблема (пока) не приходила мне в голову, поэтому я не могу сказать. Вы проверяли симулятор iOS 8? - person Martin R; 13.07.2015
comment
Нет, раньше работало, вот с iOS 8.3 и Xcode7Beta3 не работает. Более того, я не пытаюсь ничего удалять, просто добавляю инфу ... - person Bartłomiej Semańczyk; 13.07.2015
comment
@ BartłomiejSemańczyk: Тогда это вероятно ошибка iOS 9. Я бы посоветовал вам опубликовать отдельный вопрос, где вы можете добавить всю информацию, необходимую для воспроизведения этой проблемы. - person Martin R; 13.07.2015
comment
У меня такая же ошибка в Xcode 7 Beta 4, но явная проверка indexPath != newIndexPath сейчас не помогает. У кого-нибудь еще есть такая же проблема? - person tadija; 31.07.2015
comment
Можете ли вы действительно сравнить 2 экземпляра NSIndexPath на равенство просто с помощью indexPath! = NewIndexPath? Разве вам не нужно вместо этого сравнивать строку и разделы? Или используйте NSIndexPath.compare :? Или, может быть, isEqual? - person Mark Krenek; 30.08.2015
comment
@Mark: NSIndexPath наследуется от NSObject, а оператор == для NSObjects использует метод isEqual :. Сравните stackoverflow.com/a/28307428/1187415 для аналогичной проблемы. - person Martin R; 30.08.2015
comment
Все еще происходит на сборке XCode 7 GM! - person John Estropia; 10.09.2015
comment
@MartinR в объекте C, что создает проблему. Для этого нужно isEqual. - person Kunal Balani; 14.09.2015
comment
@KunalBalani: Конечно, это правильно. Вопрос, мой ответ (и приведенный выше комментарий) были написаны для Swift, а в Swift вы можете сравнить NSObjects с ==. (Сравнение указателя из Objective-C будет идентичным оператору === в Swift.) - person Martin R; 14.09.2015
comment
Уведомление делегата NSFetchedResultsController кажется полностью нарушенным в Xcode7 GM / iOS9 GM / Swift 2.0. Тот факт, что я получаю два уведомления делегата (сначала об обновлении, а затем о перемещении), если обновленное свойство является частью дескриптора сортировки, можно утверждать как законный. Но какого черта я получаю уведомление об обновлении, за которым следует уведомление о вставке в моей сущности верхнего уровня, если я создаю и устанавливаю объект отношения ?! Я даже получаю уведомление о вставке, если удаляю связанный объект. - person Christian; 16.09.2015
comment
Изменение перемещения с одним и тем же индексом источника и назначения не обязательно является ошибкой, поскольку индексы объектов могут сдвигаться из-за вставок / удалений с меньшими индексами. См. Мой комментарий на странице - person numist; 24.09.2015
comment
@MarkKrenek Чувак !!!! Я не обратил внимания на большой вопрос Это решило мою проблему: D, у меня ios 9 sdk, но я воспроизвел ошибку на ios 8.4.1 - person dave; 21.10.2015
comment
@MartinR - это исправление решило кошмарную проблему, которую я не мог исправить. Я использовал moveItems, который вызывал ошибку при попытке переместить и вставить по тому же индексу. Большое спасибо! ???????? - person some_id; 31.03.2017
comment
@DuncanBabbage: Повторите редактирование: Swift не требует скобок вокруг условия в выражении if. - person Martin R; 02.06.2017
comment
Ах, извините, я вставил ваше исправление в некоторый код Objective C, и когда Xcode подбросил жалобу, я не подумал, что через это я перенес его обратно сюда. :) - person Duncan Babbage; 02.06.2017
comment
В iOS 13.2 метод не вызывается дважды, но это создает другую проблему: если элемент ячейки переименован и полученный элемент отсортирован по новой позиции, то выполняется только вызов .move, но вызов .update отсутствует. Итак, я получаю переименованный элемент в правой строке, но его содержимое не обновляется. - person LPG; 26.02.2020

Обновление: описанная проблема возникает только на iOS 8 при сборке с iOS 9.0 или iOS 9.1 (бета) SDK.

Я придумал сегодня ужасный обходной путь после игры с Xcode 7 beta 6 (iOS 9.0 beta 5), ​​и кажется, что он работает.

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

Я по-прежнему считаю, что лучший вариант - просто позвонить reloadData.

Я считаю, что вы можете без усилий адаптировать мой код для Swift, у меня здесь есть проект objective-c.

@property NSMutableIndexSet *deletedSections, *insertedSections;

// ...

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

    self.deletedSections = [[NSMutableIndexSet alloc] init];
    self.insertedSections = [[NSMutableIndexSet alloc] init];
}

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

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
    NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:sectionIndex];

    switch(type) {
        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];
            [self.deletedSections addIndexes:indexSet];
            break;

        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];
            [self.insertedSections addIndexes:indexSet];
            break;

        default:
            break;
    }
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
    switch(type) {
        case NSFetchedResultsChangeDelete:
            [self.tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;

        case NSFetchedResultsChangeInsert:
            [self.tableView insertRowsAtIndexPaths:@[ newIndexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;

        case NSFetchedResultsChangeMove:
            // iOS 9.0b5 sends the same index path twice instead of delete
            if(![indexPath isEqual:newIndexPath]) {
                [self.tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
                [self.tableView insertRowsAtIndexPaths:@[ newIndexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
            }
            else if([self.insertedSections containsIndex:indexPath.section]) {
                // iOS 9.0b5 bug: Moving first item from section 0 (which becomes section 1 later) to section 0
                // Really the only way is to delete and insert the same index path...
                [self.tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
                [self.tableView insertRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
            }
            else if([self.deletedSections containsIndex:indexPath.section]) {
                // iOS 9.0b5 bug: same index path reported after section was removed
                // we can ignore item deletion here because the whole section was removed anyway
                [self.tableView insertRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
            }

            break;

        case NSFetchedResultsChangeUpdate:
            // On iOS 9.0b5 NSFetchedResultsController may not even contain such indexPath anymore
            // when removing last item from section.
            if(![self.deletedSections containsIndex:indexPath.section] && ![self.insertedSections containsIndex:indexPath.section]) {
                // iOS 9.0b5 sends update before delete therefore we cannot use reload
                // this will never work correctly but at least no crash. 
                UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
                [self _configureCell:cell forRowAtIndexPath:indexPath];
            }

            break;
    }
}

Только Xcode 7 / iOS 9.0

В Xcode 7 / iOS 9.0 NSFetchedResultsChangeMove все еще отправляется вместо «обновления».

В качестве простого обходного пути просто отключите анимацию в этом случае:

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
    UITableViewRowAnimation animation = UITableViewRowAnimationAutomatic;

    switch(type) {

        case NSFetchedResultsChangeMove:
            // @MARK: iOS 9.0 bug. Move sent instead of update. indexPath = newIndexPath.
            if([indexPath isEqual:newIndexPath]) {
                animation = UITableViewRowAnimationNone;
            }

            [self.tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:animation];
            [self.tableView insertRowsAtIndexPaths:@[ newIndexPath ] withRowAnimation:animation];

            break;

        // ...
    }
}
person Ben Affleck    schedule 08.09.2015
comment
Почему этому ответу не уделяется достаточно внимания? Этот обходной путь точно описывает, как ведет себя проблема. +1 - person John Estropia; 10.09.2015
comment
Мы все еще можем воспроизвести эту ошибку с помощью XCode 7 GM. - person John Estropia; 11.09.2015
comment
Честно говоря, я не получаю никаких обновлений в Xcode GM. Я получаю событие перемещения с одинаковым индексом как для старого, так и для нового пути индекса. - person Ben Affleck; 11.09.2015
comment
У меня все еще проблема с семенем Xcode 7 GM - это было первое, что я попробовал, когда они его сбросили. :( - person SimonTheDiver; 11.09.2015
comment
Я не могу поверить в это, но у меня он не вылетает в симуляторе, однако после игры с ним на устройстве под управлением iOS 8 происходит такой же сбой. - person Ben Affleck; 13.09.2015
comment
Вы, ребята, подали радар? - person Ben Affleck; 14.09.2015
comment
Любопытно, что я вижу это только на устройствах / симуляторе iOS 8.x после компиляции с семенами Xcode 7 GM. Симулятор iOS 9 больше не является для меня проблемой ... - person Arkku; 16.09.2015
comment
@Andy Я преобразовал ваш код в Swift и поделился им как суть (конечно, с благодарностью): gist .github.com / JohnEstropia / d7b25c11ba15564f0b16 - person John Estropia; 18.09.2015
comment
@ Энди, спасибо за это! Мой друг клянется, что он видел это до iOS9 и XCode 7, но это именно то, что я вижу, и замена Update на reloadData полностью исправляет. Проблема, казалось, началась, когда я сохранил новые данные в дочернем контексте в отдельном потоке. Когда таблица обновится, это вызовет эту ошибку. Спасибо, что сэкономили мне часы и часы работы. - person Brian; 18.09.2015
comment
@Brian, это все еще так с Xcode 7, выпущенным 2 дня назад? Я считаю, что эта ошибка исчезла. Я собираюсь протестировать сегодня на устройстве iOS 8. Убедитесь, что вы не тестируете предварительные версии симуляторов, которые остаются в системе даже после обновления. - person Ben Affleck; 18.09.2015
comment
Я установил XCode7 вчера, так что я считаю, что он настолько актуален, насколько это возможно. Так же тестировал только на физических устройствах. Возможно, ошибка, с которой я сталкиваюсь, не та же самая, но представляет собой то же самое, и исправление такое же - я полагаю, если он выглядит как утка и крякает как утка ... - person Brian; 18.09.2015
comment
@ Брайан ха-ха! Глядя на это в симуляторе для iOS 8.4. FRC полностью сломан. - person Ben Affleck; 18.09.2015
comment
Ошибка, из-за которой .Move и .Update публиковались для одного и того же объекта, была представлена ​​в бета-версиях iOS 9 и исправлена ​​в iOS 9 GM. Однако в iOS 8 также была ошибка корректности, которая была исправлена ​​в iOS 9, когда некоторые изменения .Move неправильно публиковались как .Update (что вызывало исключение из UIKit, когда .Move не повезло поделиться index с помощью .Delete или .Insert). Исправление доступно только для приложений, созданных для iOS 9 SDK, что означает, что приложения, работающие на iOS 8, по-прежнему должны иметь дело с более ранним поведением. - person numist; 24.09.2015
comment
CoreData по-прежнему получает: ошибка: серьезная ошибка приложения. Было обнаружено исключение от делегата NSFetchedResultsController во время вызова -controllerDidChangeContent :. попытаться удалить и перезагрузить тот же индексный путь (‹NSIndexPath: 0x18a55b90› {length = 2, path = 0–51}) с помощью userInfo (null) на устройствах iOS8.x, созданных для iOS9 в Xcode 7.0. Это исправление прекрасно работает. Пока что мне не нужно было менять последний случай NSFetchedResultsChangeUpdate. Голосовать за, за, за @Andy - person Brett; 25.09.2015
comment
Xcode 7.0.1, похоже, тоже изменил iOS 9 ... Мы наблюдали .Move изменения с indexPath == newIndexPath на устройствах iOS 9. - person John Estropia; 30.09.2015
comment
@JohnEstropia - ваше решение немного помогает. У меня есть большая служба знакомств с обменом сообщениями, обналичиванием множества коллекций и прочим, перенесенным на Swift 2.0 (это как facebook, но со знакомствами). Сможет ли яблоко это исправить? - person Nikita Semenov; 08.10.2015
comment
Эта проблема по-прежнему возникает в iOS 8 при сборке с использованием последней версии SDK для iOS 9. Обходной путь настолько болезненный (даже хуже при соединении FRC с UICollectionView), я почти склонен выполнить проверку ОС и просто перезагрузить данные для iOS 8, зарезервировав анимацию вставки / обновления / перемещения / удаления только для iOS 9 - person kball; 11.10.2015
comment
Судя по ответу, который я получил от Apple, это не будет исправлено. @JohnEstropia не стесняйтесь редактировать мой ответ и добавлять свое быстрое решение. - person Ben Affleck; 27.10.2015
comment
@Andy Можем ли мы спросить Apple о точной формулировке и контексте вашего обсуждения с ними? Трудно согласиться с тем, что это не будет исправлено, поскольку FRC полностью сломаны без какого-либо обходного пути. - person John Estropia; 27.10.2015
comment
@Andy Спасибо! Хотя это звучит безответственно с их стороны :( - person John Estropia; 27.10.2015

Что касается того, что происходит на iOS8, со сборками, скомпилированными для iOS9, помимо проблемы indexPath==newIndexPath, решаемой некоторыми другими ответами, происходит еще кое-что, что очень странно.

Перечисление NSFetchedResultsChangeType имеет четыре возможных значения (мои комментарии со значениями):

public enum NSFetchedResultsChangeType : UInt {
    case Insert // 1
    case Delete // 2
    case Move   // 3
    case Update // 4
}

.. однако функция controller:didChangeObject:atIndexPath:forChangeType иногда вызывается с недопустимым значением 0x0.

Похоже, что в этот момент Swift по умолчанию использует первый switch регистр, поэтому, если у вас есть следующая структура:

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
        switch type {
            case .Insert: tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            case .Delete: tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            case .Update: tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.None)
            case .Move: tableView.moveRowAtIndexPath(ip, toIndexPath: nip)
        }
    }

.. неверный вызов приведет к вставке, и вы получите ошибку, подобную следующей:

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

Просто поменяйте местами случаи, чтобы первый случай был довольно безобидным. Обновление устраняет проблему:

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
        switch type {
            case .Update: tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.None)
            case .Insert: tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            case .Delete: tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            case .Move: tableView.moveRowAtIndexPath(ip, toIndexPath: nip)
        }
    }

Другой вариант - проверить type.rawValue на недопустимое значение.

Примечание: хотя здесь рассматривается несколько иное сообщение об ошибке, чем сообщение, опубликованное OP, проблема связана; довольно вероятно, что как только вы исправите indexPath==newIndexPath проблему, эта проблема появится. Кроме того, приведенные выше блоки кода упрощены для иллюстрации последовательности; например, отсутствуют соответствующие guard блоки - пожалуйста, не используйте их как есть.

Кредиты: изначально это было обнаружено iCN7, источник: Форумы разработчиков Apple - причины обновления iOS 9 CoreData NSFetchedResultsController пустые строки в UICollectionView / UITableView

person magma    schedule 06.10.2015
comment
Я только что применил исправление indexPath==newIndexPath. Поскольку я использую здесь Objective-C, я не столкнулся с этим как с проблемой, однако я все еще вижу NSFetchedResultsChangeType со значением 0x0. switch не имеет подходящего case, так что проблем нет, но это все же то, на что стоит обратить внимание. - person Adam S; 06.10.2015
comment
@AdamS Я потрясен тем, что Swift выполняет case, который не соответствует предоставленному значению switch. - person magma; 06.10.2015
comment
Согласен, такое поведение вызывает беспокойство. Компилятор должен обеспечить, чтобы переключатель / регистр был исчерпывающим - в случае, если это не так (и не может быть обнаружено во время компиляции, как здесь), это должна быть ошибка времени выполнения или, по крайней мере, не должна ' не исполнять ни одного из дел! - person Adam S; 06.10.2015

По какой-то причине NSFetchedResultsController вызывает .Update, за которым следует .Move после вызова controllerWillChangeContent:.

Просто это выглядит так: НАЧАТЬ ОБНОВЛЕНИЕ -> ОБНОВЛЕНИЕ -> ПЕРЕМЕСТИТЬ -> КОНЕЦ ОБНОВЛЕНИЯ.

Бывает только под iOS 8.x

Во время одного сеанса обновления одна и та же ячейка перезагружается и удаляется, что вызывает сбой.

САМОЕ ПРОСТОЕ ИСПРАВЛЕНИЕ:

Следующая часть кода:

case .Update:
    if let indexPath = indexPath {
        tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
    }

заменить:

case .Update:
    if let indexPath = indexPath {

        // 1. get your cell
        // 2. get object related to your cell from fetched results controller
        // 3. update your cell using that object

        //EXAMPLE:
        if let cell = tableView.cellForRowAtIndexPath(indexPath) as? WLTableViewCell { //1
            let wishlist = fetchedResultsController.objectAtIndexPath(indexPath) as! WLWishlist //2
            cell.configureCellWithWishlist(wishlist) //3
        }
    }

ЭТО ДЕЙСТВИТЕЛЬНО РАБОТАЕТ.

person Bartłomiej Semańczyk    schedule 30.10.2015
comment
Думаю, я читал, что reloadRowsAtIndexPaths на самом деле является лучшим решением в целом - эта замена все еще необходима в Xcode 9? Или мы можем безопасно использовать здесь reloadRowsAtIndexPaths? - person SAHM; 14.06.2018
comment
Вот статья, о которой я имел в виду: oleb.net/blog/2013/ 02 / - person SAHM; 14.06.2018

Другие ответы были мне близки, но я получал «‹ invalid> (0x0) »в качестве NSFetchedResultsChangeType. Я заметил, что это интерпретировалось как изменение «вставки». Итак, у меня сработало следующее исправление:

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
  // iOS 9 / Swift 2.0 BUG with running 8.4
  if indexPath == nil {
    self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
  }
  (etc...)
}

Поскольку каждая «вставка» возвращается только с newIndexPath и без indexPath (и этот странный дополнительный вызов делегата вставки возвращается с тем же путем, указанным как для newIndexPath, так и для indexPath), это просто проверяет, что это правильный тип «вставки» и пропускает остальные.

person Brandon Roberts    schedule 21.09.2015

Проблема возникла из-за перезагрузки и удаления одного и того же indexPath (ошибка, созданная Apple), поэтому я изменил способ обработки сообщения NSFetchedResultsChangeUpdate.

Вместо:

 [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];

Я обновил содержимое ячейки вручную:

MyChatCell *cell = (MyChatCell *)[self.tableView cellForRowAtIndexPath:indexPath];
CoreDataObject *cdo = [[self fetchedResultsController] objectAtIndexPath:indexPath];
// update the cell with the content: cdo
[cell updateContent:cdo];

Оказывается, работает хорошо.

Кстати: обновление объекта CoreData приведет к удалению и вставке сообщения. Чтобы правильно обновить содержимое ячейки, когда indexPath равно newIndexPath (и секция, и строка равны), я перезагружаю ячейку с помощью
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];

Вот пример кода:

- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
    if (![self isViewLoaded]) return;
    switch(type)
    {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                              withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                              withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate:{
            MyChatCell *cell = (MyChatCell *)[self.tableView cellForRowAtIndexPath:indexPath];
            CoreDataObject *cdo = [[self fetchedResultsController] objectAtIndexPath:indexPath];
            // update the cell with the content: cdo
            [cell updateContent:cdo];
        }
            break;

        case NSFetchedResultsChangeMove:
            if (indexPath.row!=newIndexPath.row || indexPath.section!=newIndexPath.section){
                [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                               withRowAnimation:UITableViewRowAnimationFade];
                [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                               withRowAnimation:UITableViewRowAnimationFade];
            }else{
                [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
            }

    }
}

Я поместил приведенный выше пример кода в gist: https://gist.github.com/dreamolight/157266c615d4a226e772

person Stan    schedule 24.09.2015