I am trying to implement custom class to handle core data operations. It works great when creating new values. However when I want to update values I get nil entries in core data. Here is my code so far
/**
Update all records in given entity that matches input records
- parameters:
- entityName: name of entity to fetch
- updateBasedOnKey: name of key which will be used to identify entries that are going to be udpated
- values: NSMutableArray of all elements that are going to be updated
- important: if object with given updateBasedOnKey doesnt exist it will be created
- returns: nothing
*/
func updateRecord(entity: String, updateBasedOnKey: String, values: NSMutableArray){
let entityDescription = NSEntityDescription.entityForName(
entity, inManagedObjectContext: self.managedObjectContext)
let results = getRecords(entity)
for(elements) in values{
var newEntry = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: self.managedObjectContext)
//Determine whether to add new result or update existing
if(results.count > 0){
for result in results{
let entry = result as! NSManagedObject
if let keyValueToCompare = entry.valueForKey(updateBasedOnKey){
if (keyValueToCompare.isEqual(elements.valueForKey(updateBasedOnKey)) ){
//asign newEntry to result if found in entries
newEntry = entry
}
}
}
}
//update entry with new values
for(key, value) in elements as! NSMutableDictionary{
newEntry.setValue(value, forKey: key as! String)
}
//Try to save resulting entry
do {
try newEntry.managedObjectContext?.save()
} catch {
print(error)
}
}
}
/**
Fetch all records of given Entity in Core Data Model
- parameters:
- entityName: name of entity to fetch
- returns: NSArray of all records in given entity
*/
func getRecords(entity:String) -> NSArray{
let entityDescription = NSEntityDescription.entityForName(entity, inManagedObjectContext: self.managedObjectContext)
let fetchRequest = NSFetchRequest()
fetchRequest.entity = entityDescription
var result = NSArray()
do {
result = try self.managedObjectContext.executeFetchRequest(fetchRequest)
} catch {
let fetchError = error as NSError
print(fetchError)
}
return result
}
I think that problem is somewhere in asigning newEntry a NSManagedObject.
Any ideas how to fix this and get rid of nils?
Thanks in advance
EDIT:
this is actual working code created by implementing Wain suggestion
func updateRecord(entity: String, updateBasedOnKey: String, values: NSMutableArray){
let entityDescription = NSEntityDescription.entityForName(
entity, inManagedObjectContext: self.managedObjectContext)
let results = getRecords(entity)
for(elements) in values{
//set to true if value was already found and updated
var newEntry : NSManagedObject?
//Determine whether to add new result or update existing
if(results.count > 0){
for result in results{
let entry = result as! NSManagedObject
if let keyValueToCompare = entry.valueForKey(updateBasedOnKey){
if (keyValueToCompare.isEqual(elements.valueForKey(updateBasedOnKey)) ){
//asign newEntry to result if found in entries
newEntry = entry
}
}
}
}
if newEntry == nil {
newEntry = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: self.managedObjectContext)
}
for(key, value) in elements as! NSMutableDictionary{
newEntry!.setValue(value, forKey: key as! String)
}
}
}
You're right, the problem is that you're creating and inserting a new object each time. Instead you should be passing the object to update or running a fetch request to find it, then updating it.
It looks like your intention is to fetch, and the new entry should just be a reference, not initialised. So:
var newEntry : NSManagedObject?
Related
First off, let me explain my app and its flow. The app opens, and the user creates a profile (stores all the data through Core Data). After the user clicks on create, it sends them to a Console Screen (which displays parts of the information the user input, such as their name through a segue). Theres a tab that lets them EDIT their profile (name, weight, address, etc). When the user edits their info (to change their name, weight, etc), it should also update the info displayed on the Console Page.
I've gotten the data to save and load. The issue I'm having is when trying to edit the data from the Edit Profile screen... The user changes the text in a field, and clicks save. For some reason, the data is NOT saving...at least that's what I believe it to be the issue. When the "Save" button is pressed, the text fields go back to what the user originally input on the Create Profile screen, regardless of what text is input.
Code following...
Person.swift
// This struct would to get the data from the user
struct PInfo {
var firstName: String?
var lastName: String?
var cityState: String?
var streetAddress: String?
var gender: String?
var weight: NSNumber?
var phoneNumber: String?
var contactName: String?
var contactPhone: String?
}
func save(withPersonInfo p: PInfo, withContext context: NSManagedObjectContext) {
let entityDescription = NSEntityDescription.entity(forEntityName: "Person", in: context)
let managedObject = NSManagedObject(entity: entityDescription!, insertInto: context) as! Person
managedObject.firstName = p.firstName
managedObject.lastName = p.lastName
managedObject.cityState = p.cityState
managedObject.streetAddress = p.streetAddress
managedObject.gender = p.gender
managedObject.weight = p.weight as! Int16
managedObject.phoneNumber = p.phoneNumber
managedObject.contactName = p.contactName
managedObject.contactPhone = p.contactPhone
do {
try context.save()
print("Saved Successful")
}
catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
func fetchSingleUser(withContext context:NSManagedObjectContext) -> PInfo {
let request: NSFetchRequest<Person> = Person.fetchRequest()
let coreData_items = try? context.fetch(request)
guard let items = coreData_items,
let firstItem = items.first
else {
fatalError("Error while querying") }
print("Loaded CoreData Items: \(firstItem)")
return PInfo(firstName: firstItem.firstName!, lastName:firstItem.lastName!, cityState: firstItem.cityState!, streetAddress: firstItem.streetAddress!, gender: firstItem.gender!, weight: firstItem.weight as NSNumber, phoneNumber: firstItem.phoneNumber!, contactName: firstItem.contactName, contactPhone: firstItem.contactPhone)
}
func userDataExists(withContext context: NSManagedObjectContext) -> Bool {
let request: NSFetchRequest<Person> = Person.fetchRequest()
let coreData_items = try? context.fetch(request)
guard let items = coreData_items,
let _ = items.first
else {
return false }
return true
}
EditProfileViewController.swift
#IBAction func saveButton(_ sender: UIButton) {
//Save to CoreData
saveUsersInfo()
alertPopup(title: "Saved!", message: "Your information has been updated!")
updateTextFields()
}
func updateTextFields() {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let userInformation = fetchSingleUser(withContext: managedContext)
//Set UI Text Fields with Users Data
firstNameField.text = userInformation.firstName!
lastNameField.text = userInformation.lastName!
weightInputField.text = String(describing: userInformation.weight!)
genderInputField.text = userInformation.gender!
phoneNumberField.text = userInformation.phoneNumber!
streetAddressField.text = userInformation.streetAddress!
cityStateInputField.text = userInformation.cityState!
contactNameField.text = userInformation.contactName
contactPhoneNumberField.text = userInformation.contactPhone
print("Updated User Info Text Fields")
}
func saveUsersInfo() {
//Save to CoreData
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let userInfo = PInfo(firstName: firstNameField.text!, lastName: lastNameField.text!, cityState: cityStateInputField.text!, streetAddress: streetAddressField.text!, gender: genderInputField.text!, weight: Int16(weightInputField.text!)! as NSNumber, phoneNumber: phoneNumberField.text!, contactName: contactNameField.text!, contactPhone: contactPhoneNumberField.text!)
save(withPersonInfo: userInfo, withContext: managedContext)
print("User Info Saved")
updateTextFields()
}
}
I BELIEVE it's an issue with saving (due to debugging), but I'm not familiar enough with CoreData to know exactly what the issue is.
Any help/info is greatly appreciated!
I suspect your data is being saved. But you are creating a new object each time, rather than updating the values of the existing object. Whenever you call your save method, this line:
let managedObject = NSManagedObject(entity: entityDescription!, insertInto: context) as! Person
creates a new Person object. And when you call the fetchSingleUser method, you fetch ALL the Person objects:
let coreData_items = try? context.fetch(request)
but then use only the first of those items:
let firstItem = items.first
It happens that the first item is the original Person object, with the original values: hence the textFields revert to those original values.
If your app should have only one Person object, change the save method to fetch the existing object, and update the property values of that instance, for example in your save method:
var managedObject : Person
let request: NSFetchRequest<Person> = Person.fetchRequest()
let coreData_items = try? context.fetch(request)
if let items = coreData_items {
if items.count > 0 {
managedObject = items.first
} else {
managedObject = NSManagedObject(entity: entityDescription!, insertInto: context) as! Person
}
managedObject.firstName = p.firstName
... etc
} else {
// coreData_items is nil, so some error handling here
}
I have viewController with table view and FetchResultsController. ViewController downloads data from web and then save it to CoreData. Downloading happens every launch. I need someway to compare info that I downloaded from web and only the save it to CoreData
How can I do this ?
I had an idea of fetching all objects by fetchedResultsController.performFetch() and then assigning them to array, but I dont understand how to iterate over that array (it us [AnyObject])
Maybe there are more easy ways ?
I figured that out
I need to perform several steps in order to make comparison of content from core with array of custom objects
1) create empty array
var arrayOfReposOnDisk : [RepoObject] = []
2) fetch objects from CoreData
let fetchedData = self.fetchedResultsController.fetchedObjects
3) iterate over fetchedData and convert each value for key-value to my custom object
for i in 0 ..< fetchedData!.count {
let object = fetchedData![i]
let name = object.valueForKey("name") as! String
let description = object.valueForKey("description") as! String
let authorName = object.valueForKey("avatarURL") as! String
let avatarURL = object.valueForKey("authorName") as! String
let forks = object.valueForKey("forks") as! Int
let watches = object.valueForKey("watches") as! Int
let repoObject = RepoObject(name: name, description: description, authorName: authorName, avatarURL: avatarURL, forks: forks, watches: watches)
arrayOfItemsOnDisk.append(repoObject)
}
4) finally, make a comparison
if arrayOfReposOnDisk.contains ({ $0.name == name }) {
print("There is the same name in json as in CoreData, doing nothing")
} else {
self.insertNewObject(name, descriptionText: description, avatarURL: avatarURL, authorName: authorName, forks: forks, watches: watches)
}
You can used fetchedObjects on the fetched results controller. It is an array of [AnyObject]?, but you can cast it to your object type with something like
if let myObjects = fetchedResultsController.fetchedObjects as? [MyObjectType] {
for object in myObjects {
// do some comparison to see if the object is in your downloaded data
}
}
Another way to get the objects is to create a NSFetchRequest and use executeFetchRequest on your managed object context.
Why when I print myData array in contains at first position all my valueForKey in nil, like this
func fetchFavorites(){
let fetchRequest = NSFetchRequest(entityName: "Favoritos")
do {
let result = try moc.executeFetchRequest(fetchRequest)
for managedObject in result {
if let Fid = managedObject.valueForKey("place_favorite_id"),
desc_place = managedObject.valueForKey("desc_place"),
place_name = managedObject.valueForKey("place_name"),
lat = managedObject.valueForKey("latitude"),
lon = managedObject.valueForKey("longitude") {
print("\(Fid) \(desc_place) \(place_name) \(lat) \(lon)")
Place_Id.append(Fid as! String)
}
}
myData = result
} catch {
let fetchError = error as NSError
print(fetchError)
}
}
Printed myData Array
Array[<RivosTaxi.Favoritos: 0x7fb46ff5ac80> (entity: Favoritos; id: 0xd00000000024000e <x-coredata://82CCF746-275D-4FC6-9C5C-EFD1EDED2F21/Favoritos/p9> ; data: {
"desc_place" = nil;
latitude = nil;
longitude = nil;
"place_favorite_id" = nil;
"place_name" = nil;
}), <RivosTaxi.Favoritos: 0x7fb46ff5af80> (entity: Favoritos; id: 0xd00000000028000e <x-coredata://82CCF746-275D-4FC6-9C5C-EFD1EDED2F21/Favoritos/p10> ; data: {
"desc_place" = "";
latitude = "24.822311";
longitude = "-107.4240634";
"place_favorite_id" = 94;
"place_name" = "Otro mad";
That is what I dont understand, the first values are all nil, and I only have 1 row of data at my sqlite, not two
This is how y save it to Favoritos Entity
func FavoritosSave(){
let entity = NSEntityDescription.insertNewObjectForEntityForName("Favoritos", inManagedObjectContext: moc)
entity.setValue(place_name, forKey: "place_name")
entity.setValue(place_favorite, forKey: "place_favorite_id")
entity.setValue(desc_place, forKey: "desc_place")
entity.setValue(latitude, forKey: "latitude")
entity.setValue(longitude, forKey: "longitude")
// we save our entity
do {
try moc.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
Your fetch request is written to return all instances with entity name of "Favoritos". That search result shows that you've created two.
An explanation for only seeing one row could be that your MOC hasn't been saved. But going dumpster diving in Core Data's SQLite file is a recipe for frustration. Use the documented APIs.
You should add a call to fetchFavoritos() as the first line of your save() function, to see what's there before you create the Favoritos instance.
I use Swift. I can save data into a core data base. And I can even print out the data in a for loop but I can't load it into a table view. I have an empty string array(called subjects) that the table view uses it as a data source but I would like to load the data in a for loop into that array.
Here's how I save the data:
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var newSubject = NSEntityDescription.insertNewObjectForEntityForName("Subjects", inManagedObjectContext: context) as NSManagedObject
newSubject.setValue("" + classTextField.text, forKey: "subjectName")
context.save(nil)
Here's how I retrieve the data:
I have an empty string array in the class called subjects.
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Subjects")
request.returnsObjectsAsFaults = false
var results:NSArray = context.executeFetchRequest(request, error: nil)!
if(results.count > 0){
for res in results{
println(res)
subjects.append(res)
}
}else{
println("0 Results.")
}
So, I can print out the data as you can see in the for loop, but I can't add that res value into my subjects array which is used by the table view. But I get the AnyObject is not convertible to String error message.
Edit:
Sorry, I misunderstood the code.
You need to unwrap the results to an array of Subjects
if let resultsUnwrapped = results as? [Subjects] {
for res in resultsUnwrapped{
println(res.description())
subjects.append(res.subjectName)
}
}
This question already has answers here:
How to update existing object in Core Data?
(5 answers)
Closed 5 years ago.
I'd like to update an object in Core Data. This is my code:
var detailTaskModel: TaskModel!
detailTaskModel.date = dateFromDP //update value
let appDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
appDelegate.saveContext()
I'm using a NSFetchedResultsController and Swift.
Update:
var fetchedResultsController: NSFetchedResultsController = NSFetchedResultsController()
My func for loading the CoreData data:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: TaskCell = tableView.dequeueReusableCellWithIdentifier("myCell") as TaskCell
let thisTask = fetchedResultsController.objectAtIndexPath(indexPath) as TaskModel
cell.textLabel?.text = thisTask.task
return cell
}
Saving CoreData:
let appDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
let entityDescription = NSEntityDescription.entityForName("TaskModel", inManagedObjectContext: managedObjectContext!)
let task = TaskModel(entity: entityDescription!, insertIntoManagedObjectContext: managedObjectContext!)
task.task = labelText
You simply update any property of Core data object and call save on NSManagedObjectContext. You can also check for any changes with hasChanges method.
managedObject.setValue("newValue", forKey: "propertyName")
Update your object like above or direct call assignment and do the following
if let moc = self.managedObjectContext {
var error: NSError? = nil
if moc.hasChanges {
!moc.save(&error)
}
}
Updating an object in CoreData is quite similar to creating a new one.
First you have to fetch your object from CoreData using a fetchRequest. After that you can edit the fetched object and update it's attributes.
After that, just save it back using the managedObjectContext.
You can do this, works for me.
if let container = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer{
let context = container.viewContext
let fetchRequest = NSFetchRequest<Account>(entityName: "Account")
let name = self.txtAccount.text
fetchRequest.predicate = NSPredicate(format: "name == %#", name!)
do {
let results = try context.fetch(fetchRequest)
if (results.count > 0){
for result in results{
id = result.id
number = result.number
}
actualNumber = (number + 1)
//Update the account balance
let update = results[0]
update.number = actualNumber
try context.save()
return id
}
}catch let error {
print("Error....: \(error)")
}
You don't have to write 'result[0] as! NSManagedObject' because you have the NSFetchRequest of type Account and the constant "update" knows that.