Swift 中带有自定义 Cell 的 NSFetchedResultsController

NSFetchedResultsController with custom Cell in Swift

我想在 tableView 中有一个演示者(演讲者)列表。我正在使用 tableView 右上角的 "edit" 按钮取消隐藏或隐藏当前显示的所有对象末尾的 addSpeakerCell。这很好用并且没有问题。我可以删除对象并切换编辑按钮,一切正常。

但是当到达 addSpeakerView(通过 addSpeakerCell)并添加扬声器时,当点击保存按钮并返回到 tableView 时,会出现以下警告:

Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (0), 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)

我的猜测是 addSpeakerCell 未使用新的 indexPath 更新。但是后来还是没找到解决办法...

如果有人能为我指出正确的解决方案,甚至为我提供必要的代码片段,我将永远感激不已

我的tableView代码:

SpeakersViewController:

class SpeakersViewController: UITableViewController
    {
        var managedObjectContext: NSManagedObjectContext!

        lazy var fetchedResultsController: NSFetchedResultsController =
        {
            let fetchRequest = NSFetchRequest()
            let entity = NSEntityDescription.entityForName("Speaker", inManagedObjectContext: self.managedObjectContext)
            fetchRequest.entity = entity
            let sortDescriptor = NSSortDescriptor(key: "firstName", ascending: true)
            fetchRequest.sortDescriptors = [sortDescriptor]
            fetchRequest.fetchBatchSize = 20
            let fetchedResultsController = NSFetchedResultsController(
                fetchRequest: fetchRequest,
                managedObjectContext: self.managedObjectContext,
                sectionNameKeyPath: nil,
                cacheName: "Speaker")
            fetchedResultsController.delegate = self
            return fetchedResultsController
        }()

        var singleEdit = false // indicates user is swipe-deleting a particular speaker

        override func viewDidLoad()
        {
            super.viewDidLoad()
            self.navigationItem.rightBarButtonItem = self.editButtonItem()
            tableView.allowsSelectionDuringEditing = true

            // CoreData Stuff
            NSFetchedResultsController.deleteCacheWithName("Speaker")
            performFetch()
        }

        func performFetch()
        {
            var error: NSError?
            if !fetchedResultsController.performFetch(&error)
            {
                print("An error occurred: \(error?.localizedDescription)")
            }
        }

        deinit
        {
            fetchedResultsController.delegate = nil
        }

        override func didReceiveMemoryWarning()
        {
            super.didReceiveMemoryWarning()
        }

        // Unhide or hide the AddSpeakerCell
        override func setEditing(editing: Bool, animated: Bool)
        {
            super.setEditing(editing, animated: true)

            if !singleEdit // if user is not swipe-deleting Speaker
            {
                self.navigationItem.setHidesBackButton(editing, animated: true)
            }

            let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
            let rows = sectionInfo.numberOfObjects
            let indexPath = NSIndexPath(forRow: rows, inSection: 0)
            let indexPaths = [indexPath]
            if (editing)
            {
                tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .Top)
            }
            else
            {
                tableView.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: .Top)
            }
        }

        // MARK: - Table view data source
        override func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle
        {
            let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
            if indexPath.row == sectionInfo.numberOfObjects
            {
                return .Insert
            }
            else
            {
                return .Delete
            }
        }

        // Unhide or hide the AddSpeakerCell
        override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
        {
            let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
            var rows = sectionInfo.numberOfObjects
            if (editing)
            {
                rows++
            }
            return rows
        }

        override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
        {
            let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo

            if indexPath.row < sectionInfo.numberOfObjects
            {
                let cell = tableView.dequeueReusableCellWithIdentifier("SpeakerCell") as SpeakerCell
                let speaker = fetchedResultsController.objectAtIndexPath(indexPath) as Speaker
                cell.configureForSpeaker(speaker)
                return cell
            }
            else
            {
                let cell = tableView.dequeueReusableCellWithIdentifier("AddSpeakerCell") as UITableViewCell
                return cell
            }
        }

        override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
        {
            tableView.deselectRowAtIndexPath(indexPath, animated: true)
        }

        override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath)
        {
            if editingStyle == .Delete
            {
                let speaker = fetchedResultsController.objectAtIndexPath(indexPath) as Speaker
                managedObjectContext.deleteObject(speaker)

                var error: NSError?
                if !managedObjectContext.save(&error)
                {
                    print("An error occurred: \(error?.localizedDescription)")
                }
            }
        }

        override func tableView(tableView: UITableView, willBeginEditingRowAtIndexPath indexPath: NSIndexPath)
        {
            singleEdit = true
        }

        override func tableView(tableView: UITableView, didEndEditingRowAtIndexPath indexPath: NSIndexPath)
        {
            singleEdit = false
        }

        // MARK: - Navigation
        override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
        {
            if segue.identifier == "AddSpeaker"
            {
                let navigationController = segue.destinationViewController as UINavigationController
                let controller = navigationController.topViewController as AddSpeakerViewController
                controller.managedObjectContext = managedObjectContext
            }
        }
    }

    extension SpeakersViewController: NSFetchedResultsControllerDelegate
    {
        func controllerWillChangeContent(controller: NSFetchedResultsController)
        {
            tableView.beginUpdates()
        }

        func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
        {
            if indexPath?.section == 0
            {
                switch type
                {
                case .Insert:
                    tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
                case .Delete:
                    tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
                case .Update:
                    let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo

                    if indexPath?.row < sectionInfo.numberOfObjects
                    {
                        let cell = tableView.cellForRowAtIndexPath(indexPath!) as SpeakerCell
                        let speaker = controller.objectAtIndexPath(indexPath!) as Speaker
                        cell.configureForSpeaker(speaker)
                    }
                case .Move:
                    tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
                    tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
                }
            }
        }

        func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType)
        {
            if sectionIndex == 0
            {
                switch type
                {
                case .Insert:
                    tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
                case .Delete:
                    tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
                case .Update:
                    break
                case .Move:
                    break
                }
            }
        }

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

AddSpeakerViewController:

class AddSpeakerViewController: UITableViewController
{
    var managedObjectContext: NSManagedObjectContext!

    @IBOutlet weak var speakerFirstNameTextField: UITextField!
    @IBOutlet weak var speakerLastNameTextField: UITextField!

    override func viewDidLoad()
    {
        super.viewDidLoad()
    }
    override func didReceiveMemoryWarning()
    {
        super.didReceiveMemoryWarning()
    }
    @IBAction func cancelTapped(sender: AnyObject)
    {
        dismissViewControllerAnimated(true, completion: nil)
    }

    @IBAction func saveTapped(sender: AnyObject)
    {
        let speaker = NSEntityDescription.insertNewObjectForEntityForName("Speaker", inManagedObjectContext: managedObjectContext) as Speaker

        speaker.firstName = speakerFirstNameTextField.text

        var error: NSError?
        if !managedObjectContext.save(&error)
        {
            println("Error: \(error)")
            abort()
        }
        dismissViewControllerAnimated(true, completion: nil)
    }
}

好吧,经过几个小时的尝试和测试每一行,我终于找到了导致问题的原因。

签到

controller(didChangeObject) 

对于

indexPath?.section == 0

导致问题。我真的不知道为什么......但至少它有效。