如何使用 NSFetchedResultsController 在 table 视图控制器中添加一个部分(带有自定义 header 单元格)?

How to use NSFetchedResultsController to add a section (with a custom header cells) in a table view controller?

我正在制作一个 table 视图,该视图具有可扩展的 table 部分(束)是自定义 UITableViewCell 以及行(伙伴)——你可以结成任意一群伙伴。我也在使用 NSFetchedResultsController 填充 table,我已成功完成。我遇到的困难是在向核心数据添加一堆时,NSFetchedResultsController 抛出此异常:

CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to insert row 0 into section 1, but there are only 0 rows in section 1 after the update with userInfo (null)

我希望能够添加一堆(在模态对话框中)并让它自动显示在 table 视图中(根据 NSFetchedResultController 功能),但它会抛出异常(不会虽然崩溃了),如上所示,但未添加该部分。

这是 NSFetchedResultsController 的代码:

初始化(在加载 table 视图时初始化)

lazy var fetchedResultsController: NSFetchedResultsController = {
    let bunchesFetchRequest = NSFetchRequest(entityName: Constants.CoreData.bunch)
    // Sort bunches by bunch name, in alphabetical order, not caring about capitalization
    let primarySortDescriptor = NSSortDescriptor(key: Constants.CoreData.Bunch.name, ascending: true, selector: "caseInsensitiveCompare:")
    bunchesFetchRequest.sortDescriptors = [primarySortDescriptor]

    // TODO: Answer question: Do we need this, does this benefit us at all, and how does prefetching work?
    bunchesFetchRequest.relationshipKeyPathsForPrefetching = [Constants.CoreData.Bunch.buddies]

    let frc = NSFetchedResultsController(
        fetchRequest: bunchesFetchRequest,
        managedObjectContext: CoreDataStackManager.sharedInstance().managedObjectContext!,
        sectionNameKeyPath: Constants.CoreData.Bunch.name,
        cacheName: nil
    )
    frc.delegate = self
    return frc
}()

Table 查看方法

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    // sections are the bunches
    if let sections = fetchedResultsController.sections {
        return sections.count
    }
    return 0
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // sections are the bunches (this is for when sectionNameKeyPath is set)
    if let sections = fetchedResultsController.sections {
        let currentSection = sections[section] as! NSFetchedResultsSectionInfo
        let bunch = currentSection.objects[0] as! Bunch
        // Return the number of buddies in this section (bunch)
        return bunch.buddies.count
    }
    return 0
}


func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

    if let sections = fetchedResultsController.sections {
        let currentSection = sections[section] as! NSFetchedResultsSectionInfo
        let bunch = currentSection.objects[0] as! Bunch
            // Create BunchTableViewCell
        let headerCell: BunchTableViewCell = tableView.dequeueReusableCellWithIdentifier(bunchCellIdentifier) as! BunchTableViewCell

        headerCell.bunchNameLabel.text = bunch.name    
        return headerCell
    }
    return nil
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cell: BuddyInBunchTableViewCell = tableView.dequeueReusableCellWithIdentifier(buddyInBunchCellIdentifier, forIndexPath: indexPath) as! BuddyInBunchTableViewCell

    if let sections = fetchedResultsController.sections {
        let currentSection = sections[indexPath.section] as! NSFetchedResultsSectionInfo
        let bunch = currentSection.objects[0] as! Bunch
        let buddy: Buddy = bunch.getBuddiesInBunch()[indexPath.row]
        cell.buddyFullNameLabel.text = buddy.getFullName()
    }
    return cell
}

func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 50.0 
}

NSFetchedResultsController 方法

func controllerWillChangeContent(controller: NSFetchedResultsController) {
    self.tableView.beginUpdates()
}

func controller(
    controller: NSFetchedResultsController,
    didChangeObject anObject: AnyObject,
    atIndexPath indexPath: NSIndexPath?,
    forChangeType type: NSFetchedResultsChangeType,
    newIndexPath: NSIndexPath?) {

        switch type {
        case NSFetchedResultsChangeType.Insert:
            // Note that for Insert, we insert a row at the __newIndexPath__
            if let insertIndexPath = newIndexPath {
                // AAAAAAAAAA
                self.tableView.insertRowsAtIndexPaths([insertIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
                // BBBBBBBBBB
                // self.tableView.insertSections(NSIndexSet(index: insertIndexPath.section), withRowAnimation: UITableViewRowAnimation.Fade)
            }
        case NSFetchedResultsChangeType.Delete:
            // Note that for Delete, we delete the row at __indexPath__
            if let deleteIndexPath = indexPath {
                self.tableView.deleteRowsAtIndexPaths([deleteIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
            }
        case NSFetchedResultsChangeType.Update:
            // Note that for Update, we update the row at __indexPath__
            // Not yet implemented
            break
        case NSFetchedResultsChangeType.Move:
            // Note that for Move, we delete the row at __indexPath__
            if let deleteIndexPath = indexPath {
                self.tableView.deleteRowsAtIndexPaths([deleteIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
            }

            // Note that for Move, we insert a row at the __newIndexPath__
            if let insertIndexPath = newIndexPath {
                self.tableView.insertRowsAtIndexPaths([insertIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
            }
        }
}


func controller(
    controller: NSFetchedResultsController,
    didChangeSection sectionInfo: NSFetchedResultsSectionInfo,
    atIndex sectionIndex: Int,
    forChangeType type: NSFetchedResultsChangeType) {

        switch type {
        case .Insert:
            // AAAAAAAAAA
            let sectionIndexSet = NSIndexSet(index: sectionIndex)
            self.tableView.insertSections(sectionIndexSet, withRowAnimation: UITableViewRowAnimation.Fade)
            // BBBBBBBBBBB
            // break
        case .Delete:
            let sectionIndexSet = NSIndexSet(index: sectionIndex)
            self.tableView.deleteSections(sectionIndexSet, withRowAnimation: UITableViewRowAnimation.Fade)
        default:
            break
        }
}

func controllerDidChangeContent(controller: NSFetchedResultsController) {
    self.tableView.endUpdates()
}

注意:如果我注释掉 AAAAAAAAAA 下的行并取消注释 BBBBBBBBBB 下的行,我可以看到单元格出现得非常短暂,但随后插入的单元格下方的每个单元格都消失了,我多次收到此错误:

no index path for table cell being reused

任何 help/suggestion 不胜感激!

嗯,我发现:

"One of the bigger problems with NSFetchedResultsController is that it cannot show empty sections."

this blog post 中提到了一种绕过它的方法(注意:我还没有实现这个,但我现在知道以直观的方式是不可能的)。