Swift核心数据:在调用-controllerDidChangeContent

时间:2018-02-16 01:52:26

标签: controller nsfetchedresultscontroller

CoreData:错误:严重的应用程序错误。在调用-controllerDidChangeContent:期间,从NSFetchedResultsController的委托中捕获到异常。尝试在使用userInfo(null)

更新之前从第0部分删除第1行,该第1行只包含1行

在我的应用中 - 我有一个表视图。表视图有两个部分。该部分是使用核心数据实体中的瞬态属性来确定的,如下所示 -

@objc var section: String?{
   return event < Date() ? "before" : "after"
}

该应用应该像这样工作:

  1. 表格视图控制器允许使用单元格滑动删除/取消。当从触发委托方法的fetchedResults控制器中删除某个项目时,我会删除 performBatchUpdates 中的项目。这适用于插入和删除。

  2. 当用户选择一行时,它会转移到集合视图。视图在collectionView中滚动到所选项目(项目跨越整个视图)。用户可以在各种单元项之间来回滚动。可以从集合视图中删除项目。我也在这里使用 performBatchUpdates 来删除委托方法中的项目。这也很好。

  3. 问题发生在我从集合视图转换回表View后,只有当我碰巧删除集合View中的项目并返回tableView时才会出现问题。

  4. a)如果我插入项目 - 没问题。

    b)如果我从与我在集合视图中删除的部分相同的部分删除。它可以正常工作。

    c)上述错误仅在我尝试从其他部分(第0或第1部分)删除某个项目时显示,即我在collectionView中未删除的部分。

    示例 - 在表视图中

    **第0节**

    项目A
    项目B

    第1节

    项目C. 项目D

    如果我在收藏视图中删除项目A ,然后返回到表格视图,我会看到以下内容(这是正确的)

    删除后的表格视图:

    第0节

    项目B

    第1节

    项目C. 项目D

    但是当我尝试删除项目D或项目C.控制器认为我试图删除第0部分中的项目!

    但如果我在第0部分中首先删除了项目B,则没有问题。

    如果在删除第0部分中的项目B后删除第1部分中的项目C和D - 没有问题。

    我尝试通过在从核心数据(获取结果控制器)删除时打印indexPaths,并在触发时在didChangeObjectdidChangeSection等中打印。它会打印我正在尝试删除的正确行和部分。我正在努力解决这个问题。有没有人遇到类似问题并有解决方案?

    我的tableViewController

    var deleteMode:Bool = false
    
    lazy var fetchedResultsController:NSFetchedResultsController = { () -> NSFetchedResultsController<Event> in
        let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Event")
        request.sortDescriptors = [NSSortDescriptor(key:"eventDate",ascending:false)]
        //request.predicate = NSPredicate(format:"contact.name = %@", self.)
        let appDelegate = UIApplication.shared.delegate as? AppDelegate
        let managedObjectContext = appDelegate?.persistentContainer.viewContext
        let fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: managedObjectContext!, sectionNameKeyPath:"section", cacheName: nil)
        fetchedResultsController.delegate = self
        return fetchedResultsController as! NSFetchedResultsController<Event>
    }()
    
    viewDidLoad中的

    tableViewController

    override func viewDidLoad() {
        super.viewDidLoad()
    
        //Load data
        do{
            try fetchedResultsController.performFetch()
        }catch let error{
            print("\(String(describing: Bundle.main.bundleIdentifier)):\(error)")
        }
    
        // Other code
    }
    
     @IBAction func unwindToEvents(segue: UIStoryboardSegue){
        do{
            try self.fetchedResultsController.performFetch()
    
        }catch let error{
            print("\(String(describing: Bundle.main.bundleIdentifier)):\(error)")
        }
        eventsTableView.reloadData()
    }
    

    NSFetched结果控制器和tableView委托方法

     func numberOfSections(in tableView: UITableView) -> Int {
    
        let count = fetchedResultsController.sections?.count ?? 0
    
       // some other code...
    
        return count
    }//end of numberOfSections
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    
            guard let count = fetchedResultsController.sections?[section].numberOfObjects else{
                return 0
            }
            return count
        }
    }//end of numberOfRowsInSection
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
            let eventCell = eventsTableView.dequeueReusableCell(withIdentifier: storyBoard.eventCell, for: indexPath) as! EventTableViewCell
            eventCell.event = fetchedResultsController.object(at: indexPath) as Event
            return eventCell
        }
    }//end of cellForRowAt
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            self.currentEventIndex = indexPath
            let event = fetchedResultsController.object(at: indexPath)
            performSegue(withIdentifier: storyBoard.showEventDetail, sender: event)
        }
    }//end of tableView didSelectRowAt
    
    
    func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
            let delete = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in
                // delete item at indexPath
                let event = self.fetchedResultsController.object(at: indexPath)
                self.fetchedResultsController.managedObjectContext.delete(event)
                do{
                    self.deleteMode = true
                    try self.fetchedResultsController.managedObjectContext.save()
                }catch let error as NSError{
                     print(error.localizedDescription)
                }
            }
            let cancel = UITableViewRowAction(style: .normal, title: "Cancel") { (action, indexPath) in
                //cancel swipe action
            }
            cancel.backgroundColor = UIColor.blue
    
            return [delete,cancel]
    }//end of editActionsForRowAt
    
    
    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
            return true
    }//end of canEditRowAt
    
    
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
            guard let sectionInfo = self.fetchedResultsController.sections?[section] else {
                return " "
            }
            return sectionInfo.name //which will be the sectionNameKeyPath we provided earlier.
        }
    
    
    //MARK: NSFecthedResultscontroller delegate methods
    
    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
    
        switch type{
        case NSFetchedResultsChangeType.delete:
            let sectionIndexSet = NSIndexSet(index:sectionIndex) as IndexSet
         blockOps.append(BlockOperation(block:{
                self.eventsTableView.deleteSections(sectionIndexSet, with: .automatic)
            }))
        default:
            break
        }
    }
    
    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
            switch type{
            case .delete:
                print(indexPath!.row,indexPath!.section)
                blockOps.append(BlockOperation(block:{
                    self.eventsTableView.deleteRows(at: [indexPath!], with: .automatic)
                }))
            default:
                break
            }
    }//end of didChange anObject at
    
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        if self.deleteMode{//perform block op only for delete mode
            self.deleteMode = false
            eventsTableView.performBatchUpdates({
                for operation in blockOps{
                    operation.start()
                }
            },completion: { (completion) in
    
            })
        }
    }//end of controllerDidChangeContent
    
    }//end of EventsViewController
    

    集合视图:

    var blockOps = [BlockOperation]()
    
    lazy var fetchedResultsController:NSFetchedResultsController = { () -> NSFetchedResultsController<Event> in
        let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Event")
        request.sortDescriptors = [NSSortDescriptor(key:"eventDate",ascending:false)]
        let appDelegate = UIApplication.shared.delegate as? AppDelegate
        let managedObjectContext = appDelegate?.persistentContainer.viewContext
        let fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: managedObjectContext!, sectionNameKeyPath: "section", cacheName: nil)
        fetchedResultsController.delegate = self
        return fetchedResultsController as! NSFetchedResultsController<Event>
    }()
    
    //Other code .........
    
    @IBAction func didTapDeleteEvent(_ sender: UIButton) {
        let currentItemIndex =  eventDetailCollection.indexPathsForVisibleItems.first
        if let indexPath = currentItemIndex{
            let event = self.fetchedResultsController.object(at: indexPath)
    
            self.fetchedResultsController.managedObjectContext.delete(event)
            do{
                self.deleteMode = true
                try self.fetchedResultsController.managedObjectContext.save()
            }catch let error as NSError{
               print(error.localizedDescription)
            }
        }
    }//end of didTapDelete
    
    
    
    //The delegate methods
    
    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
        switch type{
        case .delete:
            blockOps.append(BlockOperation(block:{
                self.eventDetailCollection.deleteItems(at: [indexPath!])
            }))
        default:
            break
        }
    }//end of didChange anObject at
    
    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
        switch type{
        case NSFetchedResultsChangeType.delete:
            let sectionIndexSet = NSIndexSet(index:sectionIndex) as IndexSet
            blockOps.append(BlockOperation(block:{
                self.eventDetailCollection.deleteSections(sectionIndexSet)
            }))
        default:
            break
        }
    }//end didChange SectionInfo
    
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        if self.deleteMode{//perform block op only in delete mode
            self.deleteMode = false
            eventDetailCollection.performBatchUpdates({
                for operation in blockOps{
                    operation.start()
                }
            },completion: { (completion) in
                if let count = self.fetchedResultsController.fetchedObjects?.count {
                    if count < 1{
                        self.performSegue(withIdentifier: "detailToEvents", sender: self)
                    }
                }
            })
        }
    }//end of controllerDidChangeContent
    

1 个答案:

答案 0 :(得分:0)

我的代码中遇到了同样的问题。我仍然是编程和Swift的新手,但这里是我用于从tableview中滑动删除的代码。也许这可以帮助你的代码。

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {

    guard editingStyle == .delete else {
        return
    }

    // Fetch
    let inventory = fetchedResultsController.object(at: indexPath)

    // MARK: Delete
    // tells managed object cotext to delete the selected object
    fetchedResultsController.managedObjectContext.delete(inventory)

    // goes to the data store and performs the action
    try? fetchedResultsController.managedObjectContext.save()

    // refetches the new data store
    try? fetchedResultsController.performFetch()

    // reloads data to the tableview
    tableView.reloadData()
}