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

NSFetchedResultsController inserts the same cell into two sections only when controller is about to insert another section

这是我的 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 行。我使用以下函数在控制台上打印它:

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: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)

为什么会这样?

NSFetchedResultsController 出于某种原因将同一个单元格加载到两个部分:firstsecond。为什么?

注意:

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

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

它工作正常,即使对于第 2 部分也是如此!

为了演示 purpose,我添加了 ADD 按钮,通过按下它,代码会将当前日期字符串为 content 的新消息添加到数据库中。

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

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 屏幕截图:

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

我不知道为什么,但要使其正常工作,您需要更换:

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

fetchedResultsController.sections![section].numberOfObjects

由于某些原因objects!.count returns 不正确numberOfObjects相反的对象数量 属性.