如何在Swift中更新已保存的CoreData条目?

63

我不确定我在这里做错了什么,但当我第一次保存到coredata时,它可以正常工作。但是当我尝试覆盖它时,就会出现问题。

func testStuff() {
    var token = loadLoginData()
    println("Token \(token)")
    saveLoginData("New Token")
    var newToken = loadLoginData()
    println("Token \(newToken)")
}

func saveLoginData(accessToken: String) {
    var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
    var context: NSManagedObjectContext = appDel.managedObjectContext!
    // save data to core data
    var loginData = NSEntityDescription.insertNewObjectForEntityForName("LoginData", inManagedObjectContext: context) as NSManagedObject
    loginData.setValue(accessToken, forKey: "accessToken")
    context.save(nil)
    println("Done saving user")
}

/* Output
Token Optional("12345")
Done saving user
Token Optional("12345")
*/

加载登录数据功能,调用saveLogin数据的函数。

func loadLoginData() -> String? {
    var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
    var context: NSManagedObjectContext = appDel.managedObjectContext!

    var request = NSFetchRequest(entityName: "LoginData")
    request.returnsObjectsAsFaults = false

    var results: NSArray = context.executeFetchRequest(request, error: nil)!
    if (results.count > 0) {
        var userData: NSManagedObject = results[0] as NSManagedObject
        var accessToken: String = userData.valueForKey("accessToken") as String

        return accessToken.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())

    } else {
        println("0 results returned, potential error")
        return nil
    }
}
15个回答

63

由于batchupdate在更大的数据块中更加有用,我认为这是一种更加微妙的方法。

func saveLoginData(accessToken: String, userName: String) {
    var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
    var context: NSManagedObjectContext = appDel.managedObjectContext!

    var fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "LoginData")
    fetchRequest.predicate = NSPredicate(format: "userName = %@", userName)

    if let fetchResults = appDel.managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [NSManagedObject] {
        if fetchResults.count != 0{

            var managedObject = fetchResults[0]
            managedObject.setValue(accessToken, forKey: "accessToken")

            context.save(nil)
        }
    }
}

如果我没弄错的话,我试图将它翻译成适合你的情况,但还没有测试过。

fetchRequest.predicate基本上设置了对来自实体LoginData的属性userName进行过滤的条件,并使用在调用函数时输入的(用户)名称。假设在此示例中你只有一个与所输入名称相同的username。然后,它使用给定的过滤器执行fetchrequest,因此您可以使用setValue和在调用函数时输入的accesToken更改其值。代码在:if fetchResults.count != 0之后只有在username存在时才执行。


你必须在managedObjectContext!.executeFetchRequest之前加上appDel.,所以正确的代码行应该像这样:if let fetchResults = appDel.managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [NSManagedObject] { - GJ Nilsen
你也可以直接使用已经存储在内存中的上下文,无需再次访问它。只需使用 context.executeFetchRequest(...) 即可。 - Clay Ellis
我认为最好在try块中添加executeFetchRequest。这里没有处理错误。 - Vinu David Jose
1
请问您能否解释一下 NSPredicate(format: "userName = %@", userName) 的含义?它是带参数的 SQLite 请求结构吗? - jcmaxuser

30

Swift 5

您可以创建一个方法,既可以包括又可以更新。假设您已经在CoreData中创建了一个名为Users的实体:

 var context: NSManagedObjectContext {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    return appDelegate.persistentContainer.viewContext
 }
 let user: Users!

 let fetchUser: NSFetchRequest<Users> = Users.fetchRequest()
 fetchUser.predicate = NSPredicate(format: "id = %@", id as String)

 let results = try? context.fetch(fetchUser)

 if results?.count == 0 {
    // here you are inserting
    user = Users(context: context)
 } else {
    // here you are updating
    user = results?.first
 }

 user.id = id
 user.name = name
 ...


try context.save()

谢谢!您为我省了一些时间!我将包含我所做的微调,它们是在Web服务调用返回数据后使用的Swift UI函数中。record_id是包含在服务器上的唯一字段,因此您可以将其更改为您的“键”字段。 - Dave Kozikowski
5
实体名称应该是单数形式:Users -> User - meaning-matters
我查看了互联网上关于如何使用Core Data的几个例子,你的答案是迄今为止最容易理解的。 - André Herculano

21

更新至Swift 4 & XCode 9.2

回答您的问题...

如何在Swift中更新已经保存的CoreData条目?

首先,您需要获取对AppDelegateviewContext的引用。然后,您需要设置一个NSFetchRequest来查找您要更新的实体,在我的示例中为“Alert”。然后,根据您要查找的结果设置您的fetch。在该示例中,我的结果通过创建日期和警报类型找到了警报。

如果想进一步阅读如何使用谓词进行查询,请参考以下内容。 Stack Overflow 示例Apple 文档

我然后执行 context.fetch(fetchRequest),将结果设置为我想要更新的值,并使用try-catch处理错误。 最后我 context.save().

let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Alert")

fetchRequest.predicate = NSPredicate(format: "creationDate = %@ AND alertType = %&",
                                         argumentArray: [creationDate, alertType])

do {
    let results = try context.fetch(fetchRequest) as? [NSManagedObject]
    if results?.count != 0 { // Atleast one was returned

        // In my case, I only updated the first item in results
        results[0].setValue(yourValueToBeSet, forKey: "yourCoreDataAttribute")
    }
} catch {
    print("Fetch Failed: \(error)")
}

do { 
    try context.save() 
   }
catch {
    print("Saving Core Data Failed: \(error)")
}

1
当核心数据数据库更新时会发生什么?我是否需要以某种方式监听这些更改?谢谢。 - PoolHallJunkie
@PoolHallJunkie,你不需要监听变化,FetchRequest应该会自动更新。 - Ricky Kresslein

10

Swift >= 2现在返回一个非可选类型,并在错误情况下抛出异常,必须使用try-catch进行处理:

Swift >= 2现在返回一个非可选类型,并在错误情况下抛出异常,必须使用try-catch进行处理:

let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entity.name!)
fetchRequest.predicate = NSPredicate(format: "notificationId = 13")

do {
    let list = try context.executeFetchRequest(fetchRequest) as? [NSManagedObject]
    // Check notificationId available then not save
    if list!.count == 0 {
        let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context)
        newManagedObject.setValue("This is first message13", forKey: "message")
        newManagedObject.setValue(1, forKey: "appId")
        newManagedObject.setValue(13, forKey: "notificationId")
        newManagedObject.setValue("First one", forKey: "tital")
    }
    // success ...
} catch let error as NSError {
    // failure
    print("Fetch failed: \(error.localizedDescription)")
}

谢谢!我想在设置值后,您可能需要newManagedObject.save(),是吗? - SSH This

4
您正在创建多个新的LoginData对象,但是您的loadLoginData方法总是返回相同的对象,即来自获取请求结果的第一个对象。
您希望保持更新同一对象,因此需要更改saveLoginDetails方法。
不要创建新对象(这是insertNewObjectForEntityName所做的),而是使用loadLoginDetails方法获取现有对象,并更改其中的属性。

不,我应该每次都使用相同的。我肯定做错了。 - slooker

2
var context: NSManagedObjectContext = appDel.managedObjectContext!
var en = NSEntityDescription.entityForName("ENTITIES_NAME", inManagedObjectContext: context)
let batchUpdateRequest = NSBatchUpdateRequest(entity: en!)
batchUpdateRequest.resultType = NSBatchUpdateRequestResultType.UpdatedObjectIDsResultType
batchUpdateRequest.propertiesToUpdate = ["OBJECT_KEY": "NEWVALUE"]
var batchUpdateRequestError: NSError?
context.executeRequest(batchUpdateRequest, error: &batchUpdateRequestError)
if let error = batchUpdateRequestError {
    println("error")
}

祝你好运


2

这对我起了作用,你也应该试试:

最初的回答:

let managedContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Students", in: managedContext)
        let request = NSFetchRequest<NSFetchRequestResult>()
        request.entity = entity
        let predicate = NSPredicate(format: "(name = %@)", txtName.text!)
        request.predicate = predicate
        do {
            var results =
                try managedContext.fetch(request)
            let objectUpdate = results[0] as! NSManagedObject
            objectUpdate.setValue(txtName.text!, forKey: "name")
            objectUpdate.setValue(txtPhone.text!, forKey: "phone")
            objectUpdate.setValue(txt_Address.text!, forKey: "address")
            do {
                try managedContext.save()
                txtName.text = ""
                txtPhone.text = ""
                txt_Address.text = ""
                labelStatus.text = "Updated"
            }catch let error as NSError {
              labelStatus.text = error.localizedFailureReason
            }
        }
        catch let error as NSError {
            labelStatus.text = error.localizedFailureReason
        }

尽力使用正确的拼写、标点和语法。 - Armali

2

Swift 4.5

所有实体的通用函数

    func updateEntityObjectByKeyValue<T>(className: T.Type, key: String, value: Any, columns: [String: Any]) -> Bool {
        guard columns.keys.count != 0 else {
            return false
        }
        let context = CoreDataStack.sharedStack.mainContext
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: String(describing: className.self))

       //Depends on data type.Added fetchRequest using Int and string

        if  let sValue = value as? String {
            let predicate = NSPredicate(format: "\(key) == %@", sValue)
            fetchRequest.predicate = predicate
        } else if let iValue = value as? Int {
            let predicate = NSPredicate(format: "\(key) == %d", iValue)
            fetchRequest.predicate = predicate
        }
        
        do {
            let result = try context.fetch(fetchRequest)
            if result.count != 0 {
                if let managedObject = result[0] as? NSManagedObject {
                    for (key, value) in columns {
                        managedObject.setValue(value, forKey: key)
                    }
                    do {
                        CoreDataStack.sharedStack.saveMainContext()
                        return true
                    }
                    catch let error {
                        print(error.localizedDescription)
                    }
                }
            }
            return false
        } catch let error {
            print(error.localizedDescription)
        }
        return false
    }

使用方法:

let employeeId = your id
let isSaved = self.updateEntityObjectByKeyValue(className: EmployeeData.self, key: "id", value: employeeId, columns: ["status" :1])
    if isSaved {
       print("Saved")
     }

1

步骤1 - 创建新项目并选择“使用Core Data” 请参阅: "https://medium.com/@ankurvekariya/core-data-crud-with-swift-4-2-for-beginners-40efe4e7d1cc"

步骤2 - 在ViewController(userList)内部。
import UIKit
import CoreData

class ViewController: UIViewController, UITableViewDelegate , UITableViewDataSource {

var arrUser = Array<Any>()
@IBOutlet var tableView: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()

    self.title = "User List"
    self.tableView.separatorStyle = .none
    self.retrieveData(Delete: false, indexpath: 0)

    let logoutBarButtonItem = UIBarButtonItem(image: UIImage(named: "addImage"), style: .done, target: self, action: #selector(addNewUser))
    self.navigationItem.rightBarButtonItem  = logoutBarButtonItem
}

override func viewDidAppear(_ animated: Bool)
{
    super.viewDidDisappear(animated)
    self.retrieveData(Delete: false, indexpath: 0)
}

@objc func addNewUser(){
    let userVC = self.storyboard?.instantiateViewController(withIdentifier: "AddUser_VC") as! AddUser_VC
    self.navigationController?.pushViewController(userVC, animated: true)
}

//MARK: - TableView DataSource Delegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return self.arrUser.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell") as! UserCell
    cell.selectionStyle = .none
    let dictData = self.arrUser[indexPath.row] as? NSManagedObject
    cell.lblName.text = dictData?.value(forKey: "name") as? String ?? ""
    cell.lblPost.text = dictData?.value(forKey: "post") as? String ?? ""
    cell.lblEmail.text = dictData?.value(forKey: "email") as? String ?? ""
    cell.lblPhone.text = String(dictData?.value(forKey: "phone") as? Int ?? 0)
    cell.imageData?.image =  UIImage(data: dictData?.value(forKey: "image") as? Data ?? Data())

    return cell
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 140
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    let dictData = self.arrUser[indexPath.row] as? NSManagedObject
    let AddUserVC = self.storyboard?.instantiateViewController(withIdentifier: "AddUser_VC") as! AddUser_VC
    AddUserVC.isEdit  = true
    AddUserVC.dictObj = dictData
    self.navigationController?.pushViewController(AddUserVC, animated: true)

}

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

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
    if (editingStyle == .delete) {
        //self.deleteData(indexpath: indexPath.row)
        self.retrieveData(Delete: true, indexpath: indexPath.row)
    }
}

//MARK: - retrieveData
func retrieveData(Delete:Bool , indexpath:Int) {

    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
    let managedContext = appDelegate.persistentContainer.viewContext
    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "UserTable")

    if (Delete == true)
    {
        do
        {
            let test = try managedContext.fetch(fetchRequest)

            let objectToDelete = test[indexpath] as! NSManagedObject
            managedContext.delete(objectToDelete)

            do{
                try managedContext.save()
                self.retrieveData(Delete: false, indexpath: 0)
            }
            catch
            {
                print(error)
            }
        }
        catch
        {
            print(error)
        }
    }

    do {
        self.arrUser = try managedContext.fetch(fetchRequest)
        self.tableView.reloadData()
        print(self.arrUser)
    } catch {
         print("Failed")
    }
}
}

步骤3 - 在UserCell内部。
import UIKit

class UserCell: UITableViewCell {


@IBOutlet var lblName: UILabel!
@IBOutlet var lblEmail: UILabel!
@IBOutlet var lblPost: UILabel!
@IBOutlet var lblPhone: UILabel!
@IBOutlet var imageData: UIImageView!

override func awakeFromNib() {
    super.awakeFromNib()
    self.imageData.layer.cornerRadius = self.imageData.frame.height / 2
    self.imageData.layer.borderWidth = 1.0
    self.imageData.layer.borderColor = UIColor.lightGray.cgColor
    self.imageData.layer.masksToBounds = true
}

override func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)

    // Configure the view for the selected state
}

}

步骤4 - 在AddUserVC内部。
import UIKit
import CoreData

class AddUser_VC: UIViewController ,UIImagePickerControllerDelegate ,UINavigationControllerDelegate {

var dictObj: NSManagedObject!
var isEdit:Bool = false

@IBOutlet var imageData: UIImageView!
@IBOutlet var txtName: UITextField!
@IBOutlet var txtEmail: UITextField!
@IBOutlet var txtPost: UITextField!
@IBOutlet var txtPhone: UITextField!

@IBOutlet var btnAddUser: UIButton!


override func viewDidLoad() {
    super.viewDidLoad()

    self.imageData.layer.cornerRadius = self.imageData.frame.height / 2
    self.imageData.layer.borderWidth = 1.0
    self.imageData.layer.borderColor = UIColor.lightGray.cgColor
    self.imageData.layer.masksToBounds = true

    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped(tapGestureRecognizer:)))
    self.imageData.isUserInteractionEnabled = true
    self.imageData.addGestureRecognizer(tapGestureRecognizer)

    if (self.isEdit == true)
    {
        self.txtName.text = dictObj.value(forKey: "name") as? String ?? ""
        self.txtEmail.text = dictObj.value(forKey: "email") as? String ?? ""
        self.txtPost.text = dictObj.value(forKey: "post") as? String ?? ""
        self.txtPhone.text = String(dictObj.value(forKey: "phone") as? Int ?? 0)
        self.imageData?.image = UIImage(data: dictObj?.value(forKey: "image") as? Data ?? Data())
        self.btnAddUser.setTitle("UPDATE", for: .normal)
    }
}


//MARK: - btnAddUserAction Method -
@IBAction func btnAddUserAction(_ sender: Any) {

    let arrData = [self.txtName,self.txtEmail,self.txtPost,self.txtPhone]
    for txt in arrData
    {
        if (txt?.text == "")
        {
            let alert = UIAlertController(title: "Alert", message: "Please Enter All Fields", preferredStyle: UIAlertController.Style.alert)
            alert.addAction(UIAlertAction(title: "Click", style: UIAlertAction.Style.default, handler: nil))
            self.present(alert, animated: true, completion: nil)
            return
        }
    }

    self.createData()
}

//MARK: - Image Tap Method -
@objc func imageTapped(tapGestureRecognizer: UITapGestureRecognizer)
{
    let alert = UIAlertController(title: "Choose Image", message: nil, preferredStyle: .actionSheet)
    alert.addAction(UIAlertAction(title: "Camera", style: .default, handler: { _ in
        self.openCamera()
    }))

    alert.addAction(UIAlertAction(title: "Gallery", style: .default, handler: { _ in
        self.openGallery()
    }))

    alert.addAction(UIAlertAction.init(title: "Cancel", style: .cancel, handler: nil))

    self.present(alert, animated: true, completion: nil)
}

func openCamera()
{
    if UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera) {
        let imagePicker = UIImagePickerController()
        imagePicker.delegate = self
        imagePicker.sourceType = UIImagePickerController.SourceType.camera
        imagePicker.allowsEditing = false
        self.present(imagePicker, animated: true, completion: nil)
    }
    else
    {
        let alert  = UIAlertController(title: "Warning", message: "You don't have camera", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        self.present(alert, animated: true, completion: nil)
    }
}

func openGallery()
{
    if UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.photoLibrary){
        let imagePicker = UIImagePickerController()
        imagePicker.delegate = self
        imagePicker.allowsEditing = true
        imagePicker.sourceType = UIImagePickerController.SourceType.photoLibrary
        self.present(imagePicker, animated: true, completion: nil)
    }
    else
    {
        let alert  = UIAlertController(title: "Warning", message: "You don't have permission to access gallery.", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        self.present(alert, animated: true, completion: nil)
    }
}


func imagePickerController(_ picker: UIImagePickerController,
                           didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    guard (info[.originalImage] as? UIImage) != nil else {
        fatalError("Expected a dictionary containing an image, but was provided the following: \(info)")
    }
     self.imageData.image = info[.originalImage] as? UIImage
    picker.dismiss(animated: true, completion: nil)
}

//MARK: - createData
func createData(){

    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
    let managedContext = appDelegate.persistentContainer.viewContext
    let userEntity = NSEntityDescription.entity(forEntityName: "UserTable", in: managedContext)!

    var user = NSManagedObject()
    if(isEdit == true)
    {
         user = self.dictObj
    }
    else
    {
        user = NSManagedObject(entity: userEntity, insertInto: managedContext)
    }

    let image = self.imageData.image!.jpegData(compressionQuality: 0.5)as NSData?
    user.setValue(self.txtName.text, forKeyPath: "name")
    user.setValue(self.txtEmail.text, forKey: "email")
    user.setValue(self.txtPost.text, forKey: "post")
    user.setValue(Int(self.txtPhone.text!), forKey: "phone")
    user.setValue(image, forKey: "image")

    do {
        try managedContext.save()
        self.navigationController?.popViewController(animated: true)

    } catch let error as NSError {
        print("Could not save. \(error), \(error.userInfo)")
    }
}

} 

替代方法:
//MARK: - CoreData Methods -
func createData(){
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
    let managedContext = appDelegate.persistentContainer.viewContext
    let userEntity = NSEntityDescription.entity(forEntityName: "Movie", in: managedContext)!
    for obj in self.MoviewList.arrMovieData{
        let user = NSManagedObject(entity: userEntity, insertInto: managedContext)
        user.setValue(obj.id, forKey: "id")
        user.setValue(obj.title, forKey: "title")
        user.setValue(obj.overview, forKey: "overview")
        let url = API.IMAGE_BASE_URL + obj.poster_path
        user.setValue(url, forKey: "poster_path")
   }
    do {
        try managedContext.save()
    } catch let error as NSError {
        print("Could not save. \(error), \(error.userInfo)")
    }
}


func retrieveData() {

    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
    let managedContext = appDelegate.persistentContainer.viewContext
    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Movie")
    do {
        let result = try managedContext.fetch(fetchRequest)
        var arr = [[String:AnyObject]]()
        for data in result as! [NSManagedObject] {
            var obj = [String:AnyObject]()
            obj["id"] = data.value(forKey: "id") as AnyObject
            obj["title"] = data.value(forKey: "title") as AnyObject
            obj["overview"] = data.value(forKey: "overview") as AnyObject
            obj["poster_path"] = data.value(forKey: "poster_path") as AnyObject
            arr.append(obj)
        }
        self.MoviewList = MovieDataList(data: arr)
        self.tableView.reloadData()
    } catch {

        print("Failed")
    }
}

func deleteAllRecords() {
    let delegate = UIApplication.shared.delegate as! AppDelegate
    let context = delegate.persistentContainer.viewContext

    let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Movie")
    let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch)

    do {
        try context.execute(deleteRequest)
        try context.save()
        self.createData()
    } catch {

    }
}

enter image description here


1

//用于Swift UI,位于视图之外但在body内 :)
@Environment(.managedObjectContext) var managedObjectContext

var isNewRecord = false
let aNewContact: Contact!
let fetchContact: NSFetchRequest<Contact> = Contact.fetchRequest() as! NSFetchRequest<Contact>
fetchContact.predicate = NSPredicate(format: "record_id == %@", contactRemoteModel.contacts[i].record_id as CVarArg)
let results = try? managedObjectContext.fetch(fetchContact)

if results?.count == 0 {
    // here you are inserting
    aNewContact = Contact(context: managedObjectContext)
    aNewContact.id = UUID()
    isNewRecord = true
    print("Insert")
} else {
    // here you are updating
    aNewContact = results?.first
    print("UpDate")
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接