NSFetchedResultsController insère la même cellule dans deux sections uniquement lorsque le controller est sur le point d'insert une autre section

C'est mon file Message.swift :

 @objc(Message) class Message: NSManagedObject { @NSManaged var content: Ssortingng @NSManaged var createdAt: NSDate @NSManaged var identifier: Int64 @NSManaged var conversation: Conversation @NSManaged var sender: Consortingbutor var normalizedCreatedAt: NSDate { return createdAt.dateWithDayMonthAndYearComponents()! } } 

Voici comment je configure mon FRC:

 private func setupFetchedResultsController() { let context = NSManagedObjectContext.MR_defaultContext() let fetchRequest = NSFetchRequest(entityName: "Message") let createdAtDescriptor = NSSortDescriptor(key: "createdAt", ascending: true) fetchRequest.predicate = NSPredicate(format: "conversation.identifier = %lld", conversation.identifier) fetchRequest.sortDescriptors = [createdAtDescriptor] fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: "normalizedCreatedAt", cacheName: nil) fetchedResultsController.delegate = self try! fetchedResultsController.performFetch() tableView.reloadData() } 

avec son délégué standard.

Sur viewDidLoad mon controller a 1 section avec 1 rangée. Je l'imprime sur la console en utilisant la fonction suivante:

 func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { print("--->>>") print(section) print(fetchedResultsController.sections![section].objects!.count) return fetchedResultsController.sections![section].objects!.count } func numberOfSectionsInTableView(tableView: UITableView) -> Int { return fetchedResultsController?.sections?.count ?? 0 } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let message = fetchedResultsController?.objectAtIndexPath(indexPath) as! Message let cellIdentifier = message.sender.identifier == Settings.currentUser?.profile?.identifier ? SentTableViewCellIdentifier : ReceivedTableViewCellIdentifier let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! TableViewCell cell.cellTitleLabel?.text = message.content return cell } 

et la sortie est la suivante:

 --->>> 0 1 

Une fois que j'essaye d'append juste UN autre message avec une section différente, voici ce que j'ai:

 --->>> 0 2 --->>> 1 1 

puis l'erreur:

CoreData: erreur: Erreur d'application grave. Une exception a été interceptée par le délégué de NSFetchedResultsController lors d'un appel à -controllerDidChangeContent: Mise à jour invalide: nombre de lignes incorrect dans la section 0. Le nombre de lignes contenues dans une section existante après la mise à jour (2) doit être égal au nombre de lignes contenues dans cette section avant la mise à jour (1), plus ou less de lignes insérées ou supprimées de cette section (0 inséré, 0 supprimé) et plus ou less le nombre de lignes déplacées dans ou hors de cette section (0 déplacé, 0 déplacé). avec userInfo (null)

Pourquoi ça arrive comme ça?

NSFetchedResultsController pour une raison quelconque, charge la même cellule en deux sections: première et seconde . Pourquoi?

REMARQUE:

  • Le problème survient UNIQUEMENT lorsque FRC insère une nouvelle section. S'il doit insert une ligne dans une section existante, il n'y a pas de problème. Le problème est fort lié aux sections.
  • Le problème est UNIQUEMENT lorsque FRC essaie d'insert une SECONDE section. Quand il s'agit de la troisième ou quasortingème section, il n'y a aucun problème.

J'ai essayé votre code, fait un seul changement, ceci:

 var normalizedCreatedAt: Ssortingng { return getTimeStrWithDayPrecision(createdAt!) } func getTimeStrWithDayPrecision(date: NSDate) -> Ssortingng { let formatter = NSDateFormatter() formatter.timeStyle = .NoStyle formatter.dateStyle = .ShortStyle formatter.doesRelativeDateFormatting = true return formatter.ssortingngFromDate(date) } 

et ça marche bien, même pour la 2ème section aussi!

À des fins de démonstration, j'ai ajouté le button ADD , en appuyant dessus, le code appenda un nouveau message avec la string de date actuelle comme content dans la database.

Voici ma mise en œuvre complète: View controller-

 class ChatTableViewController: UITableViewController, NSFetchedResultsControllerDelegate { private var fetchedResultsController: NSFetchedResultsController? private var _mainThreadMOC: NSManagedObjectContext? override func viewDidLoad() { super.viewDidLoad() // Uncomment the following line to preserve selection between presentations // self.clearsSelectionOnViewWillAppear = false // Uncomment the following line to display an Edit button in the navigation bar for this view controller. // self.navigationItem.rightBarButtonItem = self.editButtonItem() setupFetchedResultsController() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } private func getMainMOC() -> NSManagedObjectContext { if _mainThreadMOC == nil { let appDel = UIApplication.sharedApplication().delegate as! AppDelegate _mainThreadMOC = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType) _mainThreadMOC!.persistentStoreCoordinator = appDel.persistentStoreCoordinator _mainThreadMOC!.undoManager = nil } return _mainThreadMOC! } private func setupFetchedResultsController() { let fetchRequest = NSFetchRequest(entityName: "Message") let createdAtDescriptor = NSSortDescriptor(key: "createdAt", ascending: true) fetchRequest.sortDescriptors = [createdAtDescriptor] fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: getMainMOC(), sectionNameKeyPath: "normalizedCreatedAt", cacheName: nil) fetchedResultsController!.delegate = self try! fetchedResultsController!.performFetch() tableView.reloadData() } @IBAction func addMessage(sender: AnyObject) { print("addMessage") let MOC = getMainMOC() let date = NSDate() let _ = Message(text: "\(date)", moc: MOC) do { try MOC.save() }catch { print("Error saving main MOC: \(error)") } } // MARK: - Table view data source override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return fetchedResultsController?.sections?.count ?? 0 } override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let sectionInfo = fetchedResultsController!.sections! as [NSFetchedResultsSectionInfo] let title = sectionInfo[section].name let headerHeight:CGFloat = tableView.sectionHeaderHeight let headerLbl = UILabel(frame: CGRectMake(0, 0, tableView.frame.width, headerHeight)) headerLbl.backgroundColor = UIColor.lightGrayColor() headerLbl.textAlignment = .Center headerLbl.text = title return headerLbl } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { print("--->>>") print(section) print(fetchedResultsController?.sections![section].objects!.count) return (fetchedResultsController?.sections![section].objects!.count)! } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let message = fetchedResultsController?.objectAtIndexPath(indexPath) as! Message let cell = tableView.dequeueReusableCellWithIdentifier("MessageCellId", forIndexPath: indexPath) cell.textLabel?.text = message.content! return cell } //MARK: - 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: AnyObject, 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: .Fade) } case .Move: if let indexPath = indexPath, let newIndexPath = newIndexPath { tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade) } } } func controllerDidChangeContent(controller: NSFetchedResultsController) { tableView.endUpdates() } } 

Message-

 func getTimeStrWithDayPrecision(date: NSDate) -> Ssortingng { let formatter = NSDateFormatter() formatter.timeStyle = .NoStyle formatter.dateStyle = .ShortStyle formatter.doesRelativeDateFormatting = true return formatter.ssortingngFromDate(date) } extension Message { @NSManaged var content: Ssortingng? @NSManaged var createdAt: NSDate? var normalizedCreatedAt: Ssortingng { return getTimeStrWithDayPrecision(createdAt!) } } class Message: NSManagedObject { // Insert code here to add functionality to your managed object subclass override init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?) { super.init(entity: entity, insertIntoManagedObjectContext: context) } init(text: Ssortingng, moc:NSManagedObjectContext) { let entity = NSEntityDescription.entityForName("Message", inManagedObjectContext: moc) super.init(entity: entity!, insertIntoManagedObjectContext: moc) content = text createdAt = NSDate() } } 

Voici la capture d'écran de l'iPad:
entrez la description de l'image ici

Pour tester plusieurs sections, j'ai changé la date et l'heure de réglage de l'iPad.

Je ne sais pas pourquoi, mais pour le faire fonctionner, vous devez replace:

 fetchedResultsController.sections![section].objects!.count 

avec

 fetchedResultsController.sections![section].numberOfObjects 

Pour une raison quelconque, les objects!.count renvoie un nombre incorrect d'objects en face de la propriété numberOfObjects .