仅当控制器要插入另一个部分时,NSFetchedResultsController才将相同的单元格插入两个部分

时间:2016-08-29 08:22:18

标签: ios swift 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行。我使用以下功能在控制台上打印它:

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的委托中捕获到异常。无效更新:第0节中的行数无效。更新(2)后现有部分中包含的行数必须等于更新前该部分中包含的行数(1),加上或减去数字从该部分插入或删除的行(0插入,0删除)和加或减移入或移出该部分的行数(0移入,0移出)。 with userInfo(null)

为什么会这样?

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

注意:

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

2 个答案:

答案 0 :(得分:1)

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

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部分也是如此!

出于演示目的,我添加了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屏幕截图:
enter image description here

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

答案 1 :(得分:0)

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

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

fetchedResultsController.sections![section].numberOfObjects

由于某种原因,objects!.count会返回与<{1}}属性相对的不正确的对象数。