从使用 CoreData 的 TableView 中删除 TableViewCell 时出错

Error deleting TableViewCell from TableView that uses CoreData

我已经尝试了所有我能找到的当前 Whosebug 建议并且 none 似乎有效,所以我必须求助于自己。

我正试图从填充有自定义单元格的 table 视图中删除日记条目。 (所有日记条目都是根据用户输入创建的,然后存储在 CoreData 中,UITableView 由来自 CoreData 的这些条目填充)。

删除 table 视图单元格时,条目被成功删除,但应用程序崩溃并出现错误

Terminating app due to uncaught exception 'NSInternalInconsistencyException'

如果我重新加载应用程序并转到带有此 tableView 的页面,则该条目不再存在于 TableView 中,因此它将从 CoreData 中删除,而不是从 table 中删除.对此的任何帮助将不胜感激!

抛出错误的函数如下:

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
print("table view index cell  \(tableView.indexPathsForVisibleRows!)")
if editingStyle == .delete {
    print("Deleting Journal Entry from Core Data")
    let managedContext = appDelegate.persistentContainer.viewContext
    managedContext.delete(fetchedResultsController.object(at: indexPath))
    do {
        try managedContext.save()
    } catch {
        print("error saving after deleting")
    }
    // THIS LINE THROWS THE ERROR :(
    tableView.deleteRows(at: [indexPath], with: .fade)
}
}

整个ViewControllerClass代码:

import UIKit
import CoreData
import Foundation

class JournalListViewController: UIViewController, UITableViewDelegate, 
UITableViewDataSource, NSFetchedResultsControllerDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate{

@IBOutlet weak var journalTableView: UITableView!
@IBOutlet weak var noEntriesLabel: UILabel!

let parks = NationalPark.getAllParks()
let dateFormatter = DateFormatter()
var journalIndex = Int()
var journalImage = UIImage()
let appDelegate = UIApplication.shared.delegate as! AppDelegate


// Core data set up
lazy var fetchedResultsController: NSFetchedResultsController<Entry> = {
  
    let managedContext = appDelegate.persistentContainer.viewContext
    let fetchRequest: NSFetchRequest<Entry> = Entry.fetchRequest()
    fetchRequest.sortDescriptors = [NSSortDescriptor (key: "park", ascending: true)]
    
    let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedContext, sectionNameKeyPath: nil, cacheName: nil)
    
    fetchedResultsController.delegate = self
    return fetchedResultsController
    
} ()



override func viewDidLoad() {
    super.viewDidLoad()
    
    self.journalTableView.delegate = self
    self.journalTableView.dataSource = self
    
    
    // Needed to Fetch Core Data
    do {
        try fetchedResultsController.performFetch()
        print ("fetched")
    } catch {
        let fetchError = error as NSError
        print("\(fetchError) , \(fetchError.localizedDescription)")
    }
}



// MARK: - Navigation

// Prepare segue to New Entry Controller
// Pass the index of list items, connected to image saving
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "toNewEntry" {
        let destinationVC = segue.destination as! NewJournalEntryViewController
        destinationVC.index = journalIndex;
    }
    else if segue.identifier == "toJournalEntryData" {
        let destinationVC = segue.destination as! JournalDataViewController
        let selectedJournalEntry: Entry = fetchedResultsController.object(at: journalTableView.indexPathForSelectedRow!)
        
        // GET IMAGE FOR SELECTED JOURNAL CELL FROM SAVED DOCUMENTS
        // then set image to UIImage for Data View
        let imageURL = getDocumentsDirectory().appendingPathComponent("image\(selectedJournalEntry.entryNum).jpg")
        let image = UIImage(contentsOfFile: imageURL.path)
        
        // print("\(journalImage)")
        
        destinationVC.journalImage = image!
        // print("THE RETURN PATH IS ****** \(imageURL)")
        
        
        // STORE INFORMATION INTO DESTINATION VIEW VARIABLES
        destinationVC.title = "Journal Entry"
        // destinationVC.journalEntryNum = selectedJournalEntry.entryNum
        destinationVC.journalTitle = selectedJournalEntry.park!
        destinationVC.journalDate = String(journalIndex)
        destinationVC.journalReport = selectedJournalEntry.report!
    }
}

func getDocumentsDirectory() -> URL {
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return paths[0]
}


// MARK: - Table View Methods

// Defines # Of Rows In TableView
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    guard let journalEntries = fetchedResultsController.fetchedObjects
    else {
        return 0
    }
    print("Journal Entries \(journalEntries.count)")
    if (journalEntries.count == 0) {
        noEntriesLabel.text = "You have no journal entries :("
        noEntriesLabel.isHidden = false
    } else { noEntriesLabel.isHidden = true
        journalIndex = journalEntries.count
    }
    return journalEntries.count
}


// Configure each table row cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    // Core Data Cell Getter
    guard let cell = tableView.dequeueReusableCell(withIdentifier: "JournalTableCell", for: indexPath) as? JournalListTableViewCell else {
        fatalError("expected Index path")
    }
    
    // Get the journalEntry number from core data to correspond with each cell
    let journalEntry = fetchedResultsController.object(at: indexPath)
    
    // SAME CODE FROM ABOVE IN THE SEGUE!
    // Gets Image from documents and sets it to the Image of the cell
    let imageURL = getDocumentsDirectory().appendingPathComponent("image\(journalEntry.entryNum).jpg")
    let image = UIImage(contentsOfFile: imageURL.path)
    
    // print("IMAGE AT CELL \(journalEntry): \(imageURL.path)")
    cell.journalImageView.image = image!
    // END SAME CODE
    
    // var dateInStringFormat = (dateFormatter.string(from: journalEntry.date!))
    cell.journalTitleLabel.text = journalEntry.park
    cell.journalDateLabel.text = String(journalEntry.entryNum)
    // cell.journalDateLabel.text = dateInStringFormat
    

    // configure cell styling
    cell.configure()
    return cell

}

func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
    return true
}

func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
    return .delete
}

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
    print("table view index cell  \(tableView.indexPathsForVisibleRows!)")
    if editingStyle == .delete {
        print("Deleting Journal Entry from Core Data")
        let managedContext = appDelegate.persistentContainer.viewContext
        managedContext.delete(fetchedResultsController.object(at: indexPath))
        do {
            try managedContext.save()
        } catch {
            print("error saving after deleting")
        }
        tableView.deleteRows(at: [indexPath], with: .fade)
    }
}


// Anytime the View Appears, refresh for new data
override func viewDidAppear(_ animated: Bool) {
    // Needed to Fetch Core Data
    do {
        try fetchedResultsController.performFetch()
        print ("fetched")
    } catch {
        let fetchError = error as NSError
        print("\(fetchError) , \(fetchError.localizedDescription)")
    }
    
    getAllItems()
}


// Simply reloads table view
func getAllItems() {
    journalTableView.reloadData()
}

}

使用 NSFetchedResultsController (FRC) 时,您不需要也不应自行删除 table 行。

只需实现 FRC 委托方法 controller(_:didChange:at:for:newIndexPath:)

然后在这个方法中做类似的事情(取决于你的部分):

    let range = NSMakeRange(0, tableView.numberOfSections)
    let sections = NSIndexSet(indexesIn: range)
    tableView.reloadSections(sections as IndexSet, with: .automatic)

所以删除tableView.deleteRows(at: [indexPath], with: .fade)并执行上面的内容。

这应该可以解决您在那条线上的崩溃问题,但我没有检查其他地方是否有问题。

祝你好运!