NSFetchedResultsController仅在控制器即将插入另一个部分时才将相同的单元插入两个部分

这是我的Message.swift文件:

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

这就是我设置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() } 

与其标准代表。

viewDidLoad我的控制器有1个部分,包含1行。 我使用以下function在控制台上打印它:

 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 } 

输出如下:

 --->>> 0 1 

一旦我尝试添加另一个不同部分的消息,我得到以下信息:

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

然后是错误:

CoreData:错误:严重的应用程序错误。 在调用-controllerDidChangeContent:期间,从NSFetchedResultsController的委托中捕获到exception。 无效更新:第0节中的行数无效。更新(2)后现有部分中包含的行数必须等于更新前该部分中包含的行数(1),加上或减去数字从该部分插入或删除的行(0已插入,0已删除)和加或减移入或移出该部分的行数(0移入,0移出)。 with userInfo(null)

为什么会这样?

NSFetchedResultsController由于某种原因将相同的单元NSFetchedResultsController载到两个部分: 第一个第二个部分 。 为什么?

注意:

  • 只有当FRC插入新部分时才会出现问题。 如果需要在现有部分中插入行,则没有问题。 问题与部分密切相关。
  • 问题仅在于FRC尝试插入SECOND部分时。 当它是第三或第四部分时,根本没有问题。

我尝试了你的代码,只做了一次改动,这个:

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

它工作正常,即使是第二节也!

为了演示目的,我添加了ADD按钮,按下它,代码将添加带有当前日期字符串作为content新消息到DB。

这是我的完整实现:​​查看控制器 –

 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() } } 

信息-

 func getTimeStrWithDayPrecision(date: NSDate) -> String { let formatter = NSDateFormatter() formatter.timeStyle = .NoStyle formatter.dateStyle = .ShortStyle formatter.doesRelativeDateFormatting = true return formatter.stringFromDate(date) } extension Message { @NSManaged var content: String? @NSManaged var createdAt: NSDate? var normalizedCreatedAt: String { 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: String, moc:NSManagedObjectContext) { let entity = NSEntityDescription.entityForName("Message", inManagedObjectContext: moc) super.init(entity: entity!, insertIntoManagedObjectContext: moc) content = text createdAt = NSDate() } } 

这是iPad屏幕截图:
在此处输入图像描述

为了测试多个部分,我更改了iPad的日期和时间设置。

我不知道为什么,但要使它工作,你需要更换:

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

 fetchedResultsController.sections![section].numberOfObjects 

由于某种原因, objects!.count返回与numberOfObjects属性相反的不正确的对象数。