无法保存到核心数据中

Posted

技术标签:

【中文标题】无法保存到核心数据中【英文标题】:Unable to save into core data 【发布时间】:2018-06-15 04:23:45 【问题描述】:

我有 2 个 ViewController 连接到选项卡栏控制器。一个 ViewController 应该显示来自核心数据的表格数据,另一个有两个输入文本字段和输入按钮以将输入数据保存到核心数据。但是,我无法使用“var managedObjectContext:NSManagedObjectContext?”进行保存。我没有使用任何 segues 来传递数据。我编码正确吗?

import UIKit
import CoreData

class AddNameViewController: UIViewController 

// MARK: - Properties

@IBOutlet var firstNameTextField: UITextField!
@IBOutlet var lastNameTextField: UITextField!

var person: Person?
var managedObjectContext: NSManagedObjectContext?


override func viewDidLoad() 
    super.viewDidLoad()

    title = "Add Name"



// MARK: - Actions

@IBAction func save(_ sender: UIButton) 
    guard let managedObjectContext = managedObjectContext else  return 

    if person == nil 
        let newName = Person(context: managedObjectContext)

        newName.firstName = firstNameTextField.text
        newName.lastName = lastNameTextField.text
        newName.createdAt = Date().timeIntervalSince1970

    
    try? managedObjectContext.save()

我可能会误导大家。这是我的表格视图代码。而且我在 AppDelegate 中没有样板。

import UIKit
import CoreData

class ViewController: UIViewController 

@IBOutlet var messageLabel: UILabel!
@IBOutlet var tableView: UITableView!
@IBOutlet var activityIndicatorView: UIActivityIndicatorView!

// MARK: - Persistent Container

private let persistentContainer = NSPersistentContainer(name: "People")

// MARK: - Fetch results controller

fileprivate lazy var fetchedResultsController:      NSFetchedResultsController<Person> = 
    let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()

    fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(Person.createdAt), ascending: true
        )]

    let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.persistentContainer.viewContext, sectionNameKeyPath: #keyPath(Person.lastName), cacheName: nil)

    fetchedResultsController.delegate = self

    return fetchedResultsController
()

override func viewDidLoad() 
    super.viewDidLoad()

    title = "People"

    persistentContainer.loadPersistentStores  (persistentSoreDescription, error) in
        if let error = error 
            print("Unable to load persistent store")
            print("\(error), \(error.localizedDescription)")
         else 
            self.setupView()

            do 
                try self.fetchedResultsController.performFetch()
             catch 
                let fetchError = error as NSError
                print("Unable to perform fetch request")
                print("\(fetchError), \(fetchError.localizedDescription)")
            

            self.updateView()
        
    

    NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackground(_:)), name: Notification.Name.UIApplicationDidEnterBackground, object: nil)


// MARK: - Views

private func setupView() 
    setupMessageLabel()

    updateView()


fileprivate func updateView() 
    var hasNames = false

    if let people = fetchedResultsController.fetchedObjects 
        hasNames = people.count > 0
    

    tableView.isHidden = !hasNames
    messageLabel.isHidden = hasNames

    activityIndicatorView.stopAnimating()


private func setupMessageLabel() 
    messageLabel.text = "You do not have names yet."


// MARK: - Notification handling

@objc func applicationDidEnterBackground(_ notification: Notification) 
    do 
        try persistentContainer.viewContext.save()
     catch 
        print("Unable to save changes")
        print("\(error), \(error.localizedDescription)")
    

extension ViewController: NSFetchedResultsControllerDelegate 

func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) 
    tableView.beginUpdates()


func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) 
    tableView.endUpdates()

    updateView()


    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) 
    switch (type) 
    case .insert:
        if let indexPath = newIndexPath 
            tableView.insertRows(at: [indexPath], with: .fade)
        
        break
    case .delete:
        if let indexPath = indexPath 
            tableView.deleteRows(at: [indexPath], with: .fade)
        
        break
    case .update:
        if let indexPath = indexPath, let cell = tableView.cellForRow(at: indexPath) as? PersonTableViewCell 
            configure(cell, at: indexPath)
        
    default:
        break

    


    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) 
    switch type 
    case .insert:
        tableView.insertSections(IndexSet(integer: sectionIndex), with: .fade)
    case .delete:
        tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
    default:
        break
    

    extension ViewController: UITableViewDataSource 
func numberOfSections(in tableView: UITableView) -> Int 
    guard let sections = fetchedResultsController.sections  else  return 0 
    return sections.count


func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
    guard let sectionInfo = fetchedResultsController.sections?[section] else  fatalError("Unexpected section") 
    return sectionInfo.numberOfObjects


func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? 
    guard let sectionInfo = fetchedResultsController.sections?[section] else  fatalError("Unexpected section") 
    return sectionInfo.name


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    guard let cell = tableView.dequeueReusableCell(withIdentifier: PersonTableViewCell.reuseIdentifier, for: indexPath) as? PersonTableViewCell else 
        fatalError("Unexpected indexPath")
    

    // Configure cell
    configure(cell, at: indexPath)

    return cell


func configure(_ cell: PersonTableViewCell, at indexPath: IndexPath) 
    let name = fetchedResultsController.object(at: indexPath)

    // Configure cell
    cell.firstNameLabel.text = name.firstName
    cell.lastNameLabel.text = name.lastName


func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) 
    if editingStyle == .delete 
        let name = fetchedResultsController.object(at: indexPath)

        name.managedObjectContext?.delete(name)
    

extension ViewController: UITabBarDelegate 
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
    tableView.deselectRow(at: indexPath, animated: true)

【问题讨论】:

数据保存后没有通知表格,请尝试重新加载表格。 你在哪里设置managedObjectContext变量? 我发布了我的其余代码。 这个查询解决了吗? 是的,已经解决了。 【参考方案1】:

我认为您需要插入要保存的新记录,如下所示

//Create Reference for New AudioFile
let newPerson = NSEntityDescription.insertNewObject(forEntityName: "Person", into: context) as! Person

//Set Values
newPerson.fileName = firstNameTextField.text
newPerson.lastName = lastNameTextField.text
newPerson.craetedAt = Date().timeIntervalSince1970

//Save Context here
self.saveChanges() 


//MARK:- Saves all changes
func saveChanges()

  do
       try context.save()
    
    catch let error as NSError
    
       // failure
       print(error)
    

另外我会建议你检查与核心数据相关的工作代码

Github 链接 - https://github.com/RockinGarg/CoreData_Swift_Demo.git

该项目使用单独的核心类(NSObject 类)进行保存、获取、按 ID 删除、删除实体数据和谓词

【讨论】:

【参考方案2】:

您的NSPersistentContainer 参考在哪里?您必须首先使用上述容器初始化您的 CoreData 堆栈,然后尝试在其上保存上下文。

尝试以下方法:

    声明你的 NSPersistentContainer 引用。

    var container: NSPersistentContainer? = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer
    

    使用容器的后台任务队列来保存上下文。

    container?.performBackgroundTask _ in
        guard let managedObjectContext = managedObjectContext else  return 
    
        if person == nil 
            let newName = Person(context: managedObjectContext)
    
            newName.firstName = firstNameTextField.text
            ...
    
        
    
        try? managedObjectContext.save() 
    
    

详情请查看docs。

    我假设样板persistentContainer 代码是您在AppDelegate 中已经拥有的东西,因为您正在使用CoreData,即,

    lazy var persistentContainer: NSPersistentContainer = 
    
        let container = NSPersistentContainer(name: "myModel")
        container.loadPersistentStores(completionHandler:  (storeDescription, error) in
            if let error = error as NSError? 
                fatalError("Unresolved error \(error), \(error.userInfo)")
            
        )
        return container
    ()
    

【讨论】:

不要将container 声明为可选。该容器必须存在以及AppDelegate。如果AppDelegate 丢失,应用程序甚至不会启动。强制转换 AppDelegate (UIApplication.shared.delegate as! AppDelegate).persistentContainer 我明白你的意思。但是,Apple 并不保证 persistentContainer 属性始终可靠。他们建议使用可选项来引用它。 This property is optional since there are legitimate error conditions that could cause the creation of the store to fail. 此外,Swift 有一些选项可以利用这种机会进行错误处理。因此,我不喜欢强制展开。尽管如此,您的方法也可以正常工作。

以上是关于无法保存到核心数据中的主要内容,如果未能解决你的问题,请参考以下文章

无法识别的选择器 - 保存到核心数据

无法将自定义对象从 API 保存到核心数据

核心数据 MOC 保存暂停执行并且无法保存(无错误或崩溃)

无法保存核心数据

将图像保存到核心数据?

核心数据:不保存