0

NSFetchedResultsControllerからコンテンツを受け取るUICollectionViewがあります。 データは複数のAPIエンドポイントからのものです(サーバーの設計は私の手にはない)。 最初の呼び出しはメタデータを配信し、各エントリ(> 10.000)よりも関連するデータを取得するためのAPI-Callが必要です。CoreDataの更新中にUICollectionViewのNSFetchedResultsControllerを置き換えるときにクラッシュする

CollectionViewは、いくつかの性質のためにフィルタリングすることができ、各フィルタは、ユーザーがフィルタリングを変更した場合ので、私は新しいものと現在のFRCを交換する必要があります異なるsectionNameKeyPath

が必要です。しかし、CoreDataが更新されると、アプリはクラッシュすることがあります

*** Assertion failure in -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.60.7/UICollectionView.m:4422 
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete item 9 from section 0 which only contains 1 items before the update' 

クラッシュを回避する方法はありますか?

EDIT:NSFetchedResultsControllerDelegateため追加コード:

#pragma mark - NSFetchedResultsControllerDelegate 

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller 
{ 
    _objectChanges = [NSMutableDictionary dictionary]; 
    _sectionChanges = [NSMutableDictionary dictionary]; 
} 

- (void)controller:(NSFetchedResultsController *)controller 
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo 
      atIndex:(NSUInteger)sectionIndex 
    forChangeType:(NSFetchedResultsChangeType)type 
{ 


    if (type == NSFetchedResultsChangeInsert || type == NSFetchedResultsChangeDelete) { 
     NSMutableIndexSet *changeSet = _sectionChanges[@(type)]; 
     if (changeSet != nil) { 
      [changeSet addIndex:sectionIndex]; 
     } else { 
      _sectionChanges[@(type)] = [[NSMutableIndexSet alloc] initWithIndex:sectionIndex]; 
     } 
    } 

} 

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject 
     atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type 
    newIndexPath:(NSIndexPath *)newIndexPath 
{ 

    NSMutableArray *changeSet = _objectChanges[@(type)]; 
    if (changeSet == nil) { 
     changeSet = [[NSMutableArray alloc] init]; 
     _objectChanges[@(type)] = changeSet; 
    } 

    switch(type) { 
     case NSFetchedResultsChangeInsert: 
      [changeSet addObject:newIndexPath]; 
      break; 
     case NSFetchedResultsChangeDelete: 
      [changeSet addObject:indexPath]; 
      break; 
     case NSFetchedResultsChangeUpdate: 
      [changeSet addObject:indexPath]; 
      break; 
     case NSFetchedResultsChangeMove: 
      [changeSet addObject:@[indexPath, newIndexPath]]; 
      break; 
    } 

} 

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller 
{ 

    NSMutableArray *moves = _objectChanges[@(NSFetchedResultsChangeMove)]; 
    if ([moves count] > 0) { 
     NSMutableArray *updatedMoves = [[NSMutableArray alloc] initWithCapacity:[moves count]]; 

     NSMutableIndexSet *insertSections = _sectionChanges[@(NSFetchedResultsChangeInsert)]; 
     NSMutableIndexSet *deleteSections = _sectionChanges[@(NSFetchedResultsChangeDelete)]; 
     for (NSArray *move in moves) { 
      NSIndexPath *oldIndexPath = move[0]; 
      NSIndexPath *newIndexPath = move[1]; 

      if ([deleteSections containsIndex:[oldIndexPath section]]) { 
       if (![insertSections containsIndex:[newIndexPath section]]) { 
        NSMutableArray *changeSet = _objectChanges[@(NSFetchedResultsChangeInsert)]; 
        if (changeSet == nil) { 
         changeSet = [[NSMutableArray alloc] initWithObjects:newIndexPath, nil]; 
         _objectChanges[@(NSFetchedResultsChangeInsert)] = changeSet; 
        } else { 
         [changeSet addObject:newIndexPath]; 
        } 
       } 
      } else if ([insertSections containsIndex:[newIndexPath section]]) { 
       NSMutableArray *changeSet = _objectChanges[@(NSFetchedResultsChangeDelete)]; 
       if (changeSet == nil) { 
        changeSet = [[NSMutableArray alloc] initWithObjects:oldIndexPath, nil]; 
        _objectChanges[@(NSFetchedResultsChangeDelete)] = changeSet; 
       } else { 
        [changeSet addObject:oldIndexPath]; 
       } 
      } else { 
       [updatedMoves addObject:move]; 
      } 
     } 

     if ([updatedMoves count] > 0) { 
      _objectChanges[@(NSFetchedResultsChangeMove)] = updatedMoves; 
     } else { 
      [_objectChanges removeObjectForKey:@(NSFetchedResultsChangeMove)]; 
     } 
    } 

    NSMutableArray *deletes = _objectChanges[@(NSFetchedResultsChangeDelete)]; 
    if ([deletes count] > 0) { 
     NSMutableIndexSet *deletedSections = _sectionChanges[@(NSFetchedResultsChangeDelete)]; 
     [deletes filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSIndexPath *evaluatedObject, NSDictionary *bindings) { 
      return ![deletedSections containsIndex:evaluatedObject.section]; 
     }]]; 
    } 

    NSMutableArray *inserts = _objectChanges[@(NSFetchedResultsChangeInsert)]; 
    if ([inserts count] > 0) { 
     NSMutableIndexSet *insertedSections = _sectionChanges[@(NSFetchedResultsChangeInsert)]; 
     [inserts filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSIndexPath *evaluatedObject, NSDictionary *bindings) { 
      return ![insertedSections containsIndex:evaluatedObject.section]; 
     }]]; 
    } 

    UICollectionView *collectionView = self.collectionView; 
    [collectionView performBatchUpdates:^{ 

     NSIndexSet *deletedSections = _sectionChanges[@(NSFetchedResultsChangeDelete)]; 
     if ([deletedSections count] > 0) { 
      [collectionView deleteSections:deletedSections]; 
     } 

     NSIndexSet *insertedSections = _sectionChanges[@(NSFetchedResultsChangeInsert)]; 
     if ([insertedSections count] > 0) { 
      [collectionView insertSections:insertedSections]; 
     } 

     NSArray *deletedItems = _objectChanges[@(NSFetchedResultsChangeDelete)]; 
     if ([deletedItems count] > 0) { 
      [collectionView deleteItemsAtIndexPaths:deletedItems]; 
     } 

     NSArray *insertedItems = _objectChanges[@(NSFetchedResultsChangeInsert)]; 
     if ([insertedItems count] > 0) { 
      [collectionView insertItemsAtIndexPaths:insertedItems]; 
     } 

     NSArray *reloadItems = _objectChanges[@(NSFetchedResultsChangeUpdate)]; 
     if ([reloadItems count] > 0) { 
      [collectionView reloadItemsAtIndexPaths:reloadItems]; 
     } 

     NSArray *moveItems = _objectChanges[@(NSFetchedResultsChangeMove)]; 
     for (NSArray *paths in moveItems) { 
      [collectionView moveItemAtIndexPath:paths[0] toIndexPath:paths[1]]; 
     } 
    } completion:^(BOOL finished) { 
     [[self refreshControl] endRefreshing]; 

    }]; 

    _objectChanges = nil; 
    _sectionChanges = nil; 

} 
+0

どのようにデリゲートコールバックを処理していますか?個々の変更をアニメーション化するだけですか? – Wain

+0

私は両方を試しました 私はperformBatchUpdatesを使用します:controllerDidChangeContentで、ブロックではitemChangesとsectionChangesの2つのmutableDictionariesを処理します。 しかし、テストのために、私は単純な "reloadData"をcontrollerDidChangeContentで試しました。 – MatzeLoCal

+0

変更処理のコードと変更内容をコアデータに保存する方法を示します – Wain

答えて

0

あなたのバッチコードが巧妙かつ効率的に見えますが、それは変更のバッチが説得力があることを保証することは非常に困難です。セクションが挿入されたり削除されたり、アイテムが移動したりすると、それは非常に難しく、他のすべてのインデックスパスが正しいことを確認して適切な項目を参照することは非常に困難です。

セクションの挿入や削除がある場合は、リロードするだけです。同時に移動したアイテムや他のものがある場合は、ただリロードします。同じセクションで複数の挿入と削除を同時に行うと、間違ったデータで画面が更新される可能性があります。

コレクション内のセクションとセクションの内容が複合的に変更されていることに本当に注意する必要があります。

FRC変更のコードは表示されません。無関係です。あなたは間違いなくスイッチをリロードする必要があります。

+0

ああ、私はあなたの "変更処理のコード"を誤解していると思います FRCの変更については、いくつか試しました。 FRC-Changeが発生する前の時点で、私はデリゲートを削除し、現在のFRCをnilに設定し、さらにCollectionViewをリロードします。次に、新しいFRC – MatzeLoCal

+0

を作成して、変更処理をコアデータとFRCデリゲートメソッドに保存するコードを用意しました。 – Wain

+0

私の問題は、batchUpdateの代わりにreloadDataがクラッシュすることになるということです – MatzeLoCal

関連する問題