これはエラーです:
CoreData:エラー:重大なアプリケーションエラー。 -controllerDidChangeContent:の呼び出し中に、NSFetchedResultsControllerのデリゲートから例外がキャッチされました。 userInfo(null)を使用して、同じインデックスパス({length = 2、path = 0-0})を削除して再ロードしようとします
これは私の典型的な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()
}
_
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()
}
}
_
何らかの理由でNSFetchedResultsController
が.Update
を呼び出した後に.Move
を呼び出し、その後にcontrollerWillChangeContent:
を呼び出します。
単純に次のようになります。BEGIN UPDATES-> [〜#〜] update [〜#〜]-> [〜#〜] move [〜# 〜]-> END UPDATES。
IOS 8.xでのみ発生します
更新の1つのセッション中に、同じセルがリロードされて削除され、クラッシュの原因となります。
これまでで最も簡単な修正:
コードの次の部分:
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
}
}
本当に機能する。
これはiOS 9(まだベータ版)のバグのようです。また、Apple Developer Forum
Xcode 7ベータ3のiOS 9シミュレーターで問題を確認できます。更新された管理対象オブジェクトでは、didChangeObject:
デリゲートメソッドがtwice:NSFetchedResultsChangeUpdate
イベントで1回、次にNSFetchedResultsChangeMove
イベント(およびindexPath == newIndexPath
)でもう一度。
上記のスレッドで提案されているindexPath != newIndexPath
の明示的なチェックを追加すると、問題が解決するようです:
case .Move:
if indexPath != newIndexPath {
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
}
更新:説明された問題は、iOS 9.0またはiOS 9.1(ベータ)SDKに対してビルドする場合にiOS 8でのみ発生します。
今日、Xcode 7ベータ6(iOS 9.0ベータ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;
// ...
}
}
iOS8で発生したこれに関して、iOS9に対してコンパイルされたビルドでは、indexPath==newIndexPath
問題に加えて、他のいくつかの回答で対処されています、何か他のことが起こります非常に奇妙です。
NSFetchedResultsChangeType
enumには4つの値があります(値のコメントは私のものです):
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)
}
}
..無効な呼び出しはInsertになり、次のようなエラーが表示されます。
無効な更新:セクション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 Developer Forums — iOS 9 CoreData NSFetchedResultsController更新によりUICollectionView/UITableView で空白行が発生する
他の回答は私にとっては近いものでしたが、NSFetchedResultsChangeTypeとして「<invalid>(0x0)」を受け取りました。 「挿入」の変更として解釈されていることに気付きました。だから、次の修正が私のために働いた:
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の両方にリストされた同じパスで戻ってくるため)、正しい種類の「挿入」であることを確認し、他をスキップします。
この問題は、同じ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
と等しい場合、セルの内容を正しく更新するには(セクションと行の両方が等しい)、Iセルをリロードする[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