C'est une erreur:
CoreData: erreur: erreur d'application grave. Une exception a été interceptée du délégué de NSFetchedResultsController lors d'un appel à -controllerDidChangeContent :. tentative de suppression et de rechargement du même chemin d'index ({longueur = 2, chemin = 0 - 0}) avec userInfo (null)
C'est ma NSFetchedResultsControllerDelegate
typique:
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()
}
dans 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()
}
}
Pour une raison quelconque, NSFetchedResultsController
appelle .Update
suivi de .Move
après que controllerWillChangeContent:
a été appelé.
Cela ressemble simplement à ceci: BEGIN UPDATES ->UPDATE->MOVE-> END UPDATES .
Se produit uniquement sous iOS 8.x
Pendant une session de mise à jour, la même cellule est rechargée et supprimée, ce qui provoque un blocage.
LE PLUS SIMPLE FIX JAMAIS:
La partie de code suivante:
case .Update:
if let indexPath = indexPath {
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
remplacer par:
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
}
}
CELA FONCTIONNE VRAIMENT .
Cela semble être un bug dans iOS 9 (qui est toujours en version bêta) et est également discuté dans le forum des développeurs Apple.
Je peux confirmer le problème avec le simulateur iOS 9 de Xcode 7 beta 3 . J'ai observé que, pour un objet géré mis à jour, la méthode de délégation didChangeObject:
est appelée deux fois: une fois avec l'événement NSFetchedResultsChangeUpdate
, puis de nouveau avec l'événement NSFetchedResultsChangeMove
(et indexPath == newIndexPath
).
Ajouter une vérification explicite pour indexPath != newIndexPath
as suggéré dans le fil de discussion ci-dessus semble résoudre le problème:
case .Move:
if indexPath != newIndexPath {
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
}
Mise à jour: le problème décrit ne se produit que sur iOS 8 lors de la compilation avec le SDK iOS 9.0 ou iOS 9.1 (bêta).
Après avoir joué avec Xcode 7 beta 6 (iOS 9.0 beta 5), j’ai eu une solution de rechange horrible aujourd’hui et il semble que cela fonctionne.
Vous ne pouvez pas utiliser reloadRowsAtIndexPaths
car, dans certains cas, il est appelé trop tôt et peut entraîner des incohérences. Vous devez plutôt mettre à jour manuellement votre cellule.
Je pense toujours que la meilleure option consiste simplement à appeler reloadData
.
Je crois que vous pouvez adapter mon code pour Swift sans effort, j'ai le projet Objective-c ici.
@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 uniquement
Dans Xcode 7/iOS 9.0, NSFetchedResultsChangeMove
est toujours envoyé au lieu de "mettre à jour".
Pour contourner le problème, désactivez simplement les animations pour ce cas:
- (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;
// ...
}
}
En ce qui concerne ce qui se passe sur iOS8, avec les compilations compilées sur iOS9, en plus du problème indexPath==newIndexPath
abordé par d'autres réponses, il se passe quelque chose d'autre qui est très étrange.
La variable NSFetchedResultsChangeType
a quatre valeurs possibles (les commentaires avec les valeurs sont les miens):
public enum NSFetchedResultsChangeType : UInt {
case Insert // 1
case Delete // 2
case Move // 3
case Update // 4
}
.. cependant, la fonction controller:didChangeObject:atIndexPath:forChangeType
est parfois appelée avec une valeur invalide 0x0
.
Swift semble passer par défaut au premier cas switch
à ce moment-là, donc si vous avez la structure suivante:
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)
}
}
.. l'appel invalide donnera lieu à une insertion et vous obtiendrez une erreur comme celle-ci:
Mise à jour non valide: nombre de lignes non valide dans la section 0. Le nombre de les lignes contenues dans une section existante après la mise à jour (7) doivent être égal au nombre de lignes contenues dans cette section avant le update (7), plus ou moins le nombre de lignes insérées ou supprimées de cette section (1 inséré, 0 supprimé)
Échanger simplement les cas pour que le premier cas soit une mise à jour plutôt inoffensive résout le problème:
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)
}
}
Une autre option serait de vérifier type.rawValue
pour une valeur non valide.
Note: bien que cela adresse un message d'erreur légèrement différent de celui envoyé par l'OP, le problème est lié; il est peu probable que dès que vous résolvez le problème indexPath==newIndexPath
, celui-ci apparaîtra .. .. De plus, les blocs de code ci-dessus sont simplifiés pour illustrer la séquence; Par exemple, les blocs guard
appropriés sont manquants - veuillez ne pas les utiliser tels quels.
Credits: ceci a été découvert par iCN7, source: Forums des développeurs Apple - La mise à jour pour iOS 9 CoreData NSFetchedResultsController entraîne l'apparition de lignes vides dans UICollectionView/UITableView
Les autres réponses étaient proches pour moi, mais je recevais "<invalid> (0x0)" en tant que NSFetchedResultsChangeType. J'ai remarqué que cela était interprété comme un changement "d'insertion". Donc, le correctif suivant a fonctionné pour moi:
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...)
}
Comme chaque "insert" ne revient qu'avec un newIndexPath et aucun indexPath (et cet étrange appel de délégué d'insertion supplémentaire revient avec le même chemin que celui indiqué pour newIndexPath et indexPath), ceci vérifie simplement que c'est le bon type "d'insertion" et saute les autres.
Le problème est dû au rechargement et à la suppression du même indexPath (ce qui est un bogue produit par Apple). Je change donc la façon dont je gère le message NSFetchedResultsChangeUpdate
.
Au lieu de:
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
J'ai mis à jour le contenu de la cellule manuellement:
MyChatCell *cell = (MyChatCell *)[self.tableView cellForRowAtIndexPath:indexPath];
CoreDataObject *cdo = [[self fetchedResultsController] objectAtIndexPath:indexPath];
// update the cell with the content: cdo
[cell updateContent:cdo];
Cela fonctionne bien.
BTW: La mise à jour de l’objet CoreData produirait une suppression et un message d’insertion. Pour mettre à jour correctement le contenu de la cellule, lorsque la indexPath
est égale à la newIndexPath
(la section et la ligne sont égales), je recharge la cellule par[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
Voici l exemple de code:
- (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];
}
}
}
J'ai mis l'exemple de code ci-dessus dans Gist: https://Gist.github.com/dreamolight/157266c615d4a226e772