Swift - Updating entries in core data - swift

Context:
A game, at the end of the game the user retrieves a score. A function is called to save the score to the DB
Logic:
Query the DB to check if a record exists
If a record exists, retrieve the score, if the current game score is higher then the score retrieved then update the record
If you are new user then make a new entry into DB
Issues
It keeps saving new entries to the DB and not updating the 1st entry. I only want the user to have the 1 entry that gets updated as they keep getting higher scores.
Is there anywhere I can see the localDB to see what's going on?
Also this seems verbose, is there an easier way?
Code
func saveName(score: Int, whereToStore: String) {
//1
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Scores")
//2
let entity = NSEntityDescription.entityForName("Scores",
inManagedObjectContext:managedContext)
let yourScore = NSManagedObject(entity: entity!,
insertIntoManagedObjectContext: managedContext)
/*Fetch results */
do
{
let fetchedResult = try managedContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]
if let results = fetchedResult
{
if results.count > 1{
//Do some update code here if existing record in DB
print("Code to update number one result")
//yourScore.setValue(score, forKey: whereToStore)
let updateScore = results[0].valueForKey(whereToStore) as! Int
let updateScoreObject = results[0]
print("Score got back is \(updateScore)")
if (score > updateScore){
print("time to update")
updateScoreObject.setValue(score, forKey: whereToStore)
do {
try updateScoreObject.managedObjectContext!.save()
print("updated \(updateScoreObject)")
} catch {
let saveError = error as NSError
print(saveError)
}
}
} else {
// Do the saving here if you are a new user
//3
yourScore.setValue(score, forKey: whereToStore)
print("DOES THIS CODE RUN")
print("your score is\(yourScore)")
//4
do {
try managedContext.save()
//5
print("worked, I saved")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
}
}
else
{
print("Could not fetch result")
}
}
catch
{
print("There is some error.")
}
}
Update
Added
let documentsUrl = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
print("\(documentsUrl)")
So I can find the local SQLite and opened up with a DB viewer, as expected it keeps saving new entries rather than updating the 1st

Related

How to update looping data into core data?

I have tableView, and I already succeed saved it into core data (create new in core data).
When I update, it only update base on last data, not all data, and it cause the other data filled with the last data. so the result like this:
the actual is the first and the second tableView cell data different, but after update, it copy last data to other data.
This is my update code :
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequestPhone = NSFetchRequest<NSManagedObject>(entityName: "Phone")
do {
let phones = try managedContext.fetch(fetchRequestPhone)
if (phones.count > 0) {
for phone in phones {
phone.setValue(phoneNameValue, forKey: "phoneName")
phone.setValue(phoneNumberValue, forKey: "phoneNumber")
do{
try managedContext.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
}
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
What is the correct code to repair this?

Problems saving and retrieving numbers

I am working on an app that uses CoreData.
I am strong numbers and retrieving them, but when i store more than one field it only shows the last field with the number but a 0 in all the others. below is my codes.
to store them...
let CWConvert = Double(CWeight.text!)
storeTranscription18CW(pageText: (CWConvert)!, textFileUrlString: "cWeight")
savePlus += 1
let TWConvert = Double(TWeight.text!)
storeTranscription18TW(pageText: (TWConvert)!, textFileUrlString: "tWeight")
and...
func getContext () -> NSManagedObjectContext {
_ = UIApplication.shared.delegate as! AppDelegate
return DataController().managedObjectContext
}
func storeTranscription18CW (pageText: Double, textFileUrlString: String) {
let context = getContext()
//retrieve the entity that we just created
let entity = NSEntityDescription.entity(forEntityName: "TextInputs", in: context)
let transc = NSManagedObject(entity: entity!, insertInto: context)
// set the entity values
transc.setValue(pageText, forKey: "cWeight")
//save the object
do {
try context.save()
print("saved!")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
}
}
func storeTranscription18TW (pageText: Double, textFileUrlString: String) {
let contexta = getContext()
//retrieve the entity that we just created
let entitya = NSEntityDescription.entity(forEntityName: "TextInputs", in: contexta)
let transc = NSManagedObject(entity: entitya!, insertInto: contexta)
// set the entity values
transc.setValue(pageText, forKey: "tWeight")
//save the object
do {
try contexta.save()
print("saved!")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
}
}
and to retrive.
func getTranscriptions18CW () {
//create a fetch request, telling it about the entity
let fetchRequesta: NSFetchRequest<TextInputs> = TextInputs.fetchRequest()
do {
//go get the results
let searchResults18CW = try getContext().fetch(fetchRequesta)
for transa in searchResults18CW as [NSManagedObject] {
if let resulta = transa.value(forKey: "cWeight") {
if let stra = resulta as? String {
CWeight.text = stra
}else {
CWeight?.text = "\(resulta)"
}
}
}
//get the Key Value pairs (although there may be a better
}catch {
print("Error with request: \(error)")
}
}
func getTranscriptions18TW () {
//create a fetch request, telling it about the entity
let fetchRequest: NSFetchRequest<TextInputs> = TextInputs.fetchRequest()
do {
//go get the results
let searchResults18TW = try getContext().fetch(fetchRequest)
for trans in searchResults18TW as [NSManagedObject] {
if let result = trans.value(forKey: "tWeight") {
if let str = result as? String {
TWeight.text = str
}else {
TWeight?.text = "\(result)"
}
}
//get the Key Value pairs (although there may be a better way to do that...
}
}catch {
print("Error with request: \(error)")
}
}
i have tried different names but get the same it only shows the last one as the real number if i moment out the last one then the first shows the real value.
Try something more like this to save so that you are saving both in the same row.
func storeTranscription18TW () {
let contexta = getContext()
//retrieve the entity that we just created
let entitya = NSEntityDescription.entity(forEntityName: "TextInputs", in: contexta)
let transc = NSManagedObject(entity: entitya!, insertInto: contexta)
// set the entity values
transc.setValue(CWConvert, forKey: "cWeight") // Note the change from pageText
transc.setValue(TWConvert, forKey: "tWeight") // Note the change from pageText
//save the object
do {
try contexta.save()
print("saved!")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
}
}
Then when you do a get they would both be done in the same function as well.

Understanding Core Data When Deleting Objects that Depend on Each Other

This question is asking for the best practice in the following scenario:
Attached are images showing my work orders and services core data entities. Note that the Delete Rule is currently No Action for Work Order. (Note changing to Nullify will not fix my issue, just causes same issue). Also take note that on Service I have constraints on id. This won't allow duplicates. As such I aded a merge policy below:
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
The merge policy will take the new data I send and overwrite what is in the database as the default. Without this my program will throw an error with how its written.
If I run my code with these settings, and I do a batch delete on workorders BUT NOT SERVICES (because I want to keep those) what happens is when I restart my program it crashes when I try to add **a reference to a Service with the same id.
My question is why would it crash and what is the best way to work around this? My current theory is that these entities might have another unique identifier and because I deleted the work order its reference was to a different contexted version of services... and when I create the new one using the same id as the old services it assumes the same internal id possibly. I am not sure if this is happening though or how to confirm that.
My code happens in viewDidLoad method of one of my controllers and looks like this.
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
let context = gm_getContext()
//Create default fetch request to get all workorders
let fetchRequest: NSFetchRequest<Workorders> = Workorders.fetchRequest()
do{
//Run fetch request to get search results.
let searchResults = try context.fetch(fetchRequest)
//If no results were found and demo mode = true, lets create some default records.
if(searchResults.count<=0 && g_demoMode==true){
print("create default data")
//Uncomment the following lines if you want to prove that the Merge Policy
//Is working for Unique Constraints.
let serviceFetchRequest: NSFetchRequest<Service> = Service.fetchRequest()
let serviceSearchResults = try context.fetch(serviceFetchRequest)
print("Services Count = \(serviceSearchResults.count)")
//First we have to create a sample service
let entity = NSEntityDescription.entity(forEntityName: "Service", in: context)
let service = NSManagedObject(entity: entity!, insertInto: context)
service.setValue(1, forKey: "id")
service.setValue("Tire Repair Service Sample", forKey: "name")
service.setValue("<html>Test Service Field</html>",forKey:"templatedata")
//add reference to the global
g_services.append(service as! Service)
//Proof that service is indeed a Service object and stored in global
print("g_services[0].name = "+g_services[0].name!)
//Save the service object (overwriting an old one with same id if needed)
do {
try context.save()
print("Saved context with service")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
print("Could not save, unknown error")
}
//Now create 3 sample work orders all using the same service template.
let workorderEntity1 = NSEntityDescription.entity(forEntityName: "Workorders", in: context)
let workorder1 = NSManagedObject(entity: workorderEntity1!, insertInto: context)
print("created work order variable 1")
workorder1.setValue(1, forKey: "id")
workorder1.setValue("11402 Kensington Rd, Los Alamitos, CA, 90720", forKey: "address")
workorder1.setValue("33.797472", forKey: "lat")
workorder1.setValue("-118.084136", forKey: "lng")
workorder1.setValue(15,forKey: "client_id")
workorder1.setValue("Need to fix their tire fast", forKey: "desc")
workorder1.setValue("(562)810-4384", forKey: "phone")
workorder1.setValue(g_services[0], forKey: "service")
print("Created first work order")
let workorderEntity2 = NSEntityDescription.entity(forEntityName: "Workorders", in: context)
let workorder2 = NSManagedObject(entity: workorderEntity2!, insertInto: context)
workorder2.setValue(2, forKey: "id")
workorder2.setValue("17078 Greenleaf Street, Fountain Valley, CA, 92708", forKey: "address")
workorder2.setValue("33.714992", forKey: "lat")
workorder2.setValue("-117.958874", forKey: "lng")
workorder2.setValue(16,forKey: "client_id")
workorder2.setValue("This guy does not know what he wants", forKey: "desc")
workorder2.setValue("(562)777-3344", forKey: "phone")
workorder2.setValue(g_services[0], forKey: "service")
let workorderEntity3 = NSEntityDescription.entity(forEntityName: "Workorders", in: context)
let workorder3 = NSManagedObject(entity: workorderEntity3!, insertInto: context)
workorder3.setValue(3, forKey: "id")
workorder3.setValue("17045 South Pacific Avenue", forKey: "address")
workorder3.setValue("33.713565", forKey: "lat")
workorder3.setValue("-118.067535", forKey: "lng")
workorder3.setValue(17,forKey: "client_id")
workorder3.setValue("Tire damaged by the beach", forKey: "desc")
workorder3.setValue("(714)234-5678", forKey: "phone")
workorder3.setValue(g_services[0], forKey: "service")
//Don't need signature, pictures and videos because they just don't exist yet.
//add reference to the global
g_workOrders.append(workorder1 as! Workorders)
g_workOrders.append(workorder2 as! Workorders)
g_workOrders.append(workorder3 as! Workorders)
print("Preparing to save to context for work orders")
//Save the work order objects (overwriting any old ones with same id if needed)
do {
try context.save()
print("Saved context with workorders")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
print("Could not save, unknown error")
}
}else{
print("WorkOrders Count = \(searchResults.count)")
let workorderFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Workorders")
//let workorderFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Workorders")
let deleteWorkOrderRequest = NSBatchDeleteRequest(fetchRequest: workorderFetchRequest) //Deletes ALL workorders
//Perform Actual Deletion On Database Tables
do{
try context.persistentStoreCoordinator!.execute(deleteWorkOrderRequest, with: context)
}catch{
fatalError("Bad Things Happened \(error)")
}
print("deleted workorders")
}
} catch {
print("Error with request: \(error)")
}
print("service table view controller loaded")
}
My context and global variables to track the coreData values are defined globally in a globals.swift file like this.
var g_workOrders = [Workorders]()
var g_services = [Service]()
//Shortcut method to get the viewcontext easily from anywhere.
func gm_getContext () -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
//For unique constraints it will overwrite the data.
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return context
}
Core Data Model References:
Other Notes & Things I've Tried:
I know it crashes at this line (workorder1.setValue(g_services[0], forKey: "service")), which is how I know its related to service, and changing the rule to cascade delete for workorders fixes the crash however it deletes the Services that were attached to it! ... which makes sense but not what I wanted.
I have recently found the answer to my question, and the problem is related to multiple things.
First my core data stack was set incorrectly. I've now changed it to this (courtesy my friendly developer friend who pointed this out).
import UIKit
import CoreData
class DataController: NSObject {
var managedObjectContext: NSManagedObjectContext
static var dataController: DataController!
override init() {
// This resource is the same name as your xcdatamodeld contained in your project.
guard let modelURL = Bundle.main.url(forResource: "WorkOrders", withExtension: "momd") else {
fatalError("Error loading model from bundle")
}
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
guard let mom = NSManagedObjectModel(contentsOf: modelURL) else {
fatalError("Error initializing mom from: \(modelURL)")
}
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = psc
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let docURL = urls[urls.endIndex-1]
/* The directory the application uses to store the Core Data store file.
This code uses a file named "DataModel.sqlite" in the application's documents directory.
*/
let storeURL = docURL.appendingPathComponent("WorkOrders.sqlite")
do {
let options = [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
try psc.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options)
} catch {
fatalError("Error migrating store: \(error)")
}
}
class func sharedInstance() -> DataController {
if (dataController != nil) {
return dataController
}
dataController = DataController()
return dataController
}
}
Whenever I need to access coreData I should be doing it this way now...
let context = DataController.sharedInstance().managedObjectContext
Another thing to note is the concurrency setting in the Datacontroller is set to work on the main thread. This was also part of the problem since I was running my code in a thread.
Its set to the main thread on this line in DataController
managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
So everytime you are going to access or save data to coreData always wrap it in a call to the main thread like below...
DispatchQueue.main.async {
AppDelegate.appDelegate.saveContext()
}
Finally, the last problem I had was I was doing a batch delete with the following command below.
let workorderFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Workorders")
let deleteWorkOrderRequest = NSBatchDeleteRequest(fetchRequest: workorderFetchRequest) //Deletes ALL workorders
let context = DataController.sharedInstance().managedObjectContext
//Save the work order objects (overwriting any old ones with same id if needed)
do {
try context.execute(deleteWorkOrderRequest)
context.reset()
print(">>> cleared old data!")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
print("Could not save, unknown error")
}
The key here is understanding that batch commands currently work directly on the database and ignore the managed context, this means my managed context and database were getting out of sync after I ran this command. The easy fix is to always make sure after doing batch commands to run...
context.reset()
This will forcefully load back the data from the database into the managed context so everything is in sync. After I made these changes everything worked fine. Hope this helps someone.

Sync Core Data with server - same objects added multiple times

I have a list of categories that my user can choose. Since I am planning to add new categories without having to send a new update, I would like to load the new categories from my server every time a user launch the app. I came up with this code but every time it runs it keeps adding the same categories.
func loadDealCategory(){
let myUrl = NSURL(string: "\(ipAddress)/v1.0/dealCategory.php")
let request = NSMutableURLRequest(URL: myUrl!)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{ data, response, error in
if error != nil {
print("error\(error)")
}else{
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as! NSArray
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
for jsonDictionary in json {
// listing of columns in JSON seed file
let categoryDescription = jsonDictionary.valueForKey("category_description") as! String
print(categoryDescription)
let newCategory = NSEntityDescription.insertNewObjectForEntityForName("Categories", inManagedObjectContext: managedContext)
newCategory.setValue(categoryDescription, forKey: "category_description")
// I tried also format: "category_description = %#"
let fetchRequest = NSFetchRequest(entityName: "Categories")
fetchRequest.predicate = NSPredicate(format: "category_description LIKE %#", categoryDescription)
do {
let fetchResults = try managedContext.executeFetchRequest(fetchRequest)
print(fetchResults)
if fetchResults.count == 0{
do {
try managedContext.save()
//5
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
}else{
print("\(categoryDescription) already exist!")
}
} catch let error as NSError {
// failure
print("Fetch failed: \(error.localizedDescription)")
}
}
} catch{
print("something went wrong loading json")
}
}
}
task.resume()
}
is it ok , loading it in func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
Found the problem, I was inserting the object before checking if it was already in the entity.
if fetchResults.count == 0{
let newCategory = NSEntityDescription.insertNewObjectForEntityForName("Categories", inManagedObjectContext: managedContext)
newCategory.setValue(categoryDescription, forKey: "category_description")
}else{
print("Could not save \(error), \(error.userInfo)")
}
and only then save
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}

Update attribute for specific element using core data

I am working with core data to store different activities and keep track of how many times each activity has been performed. The entity is called "Activities" with attributes "name" and "total". When an activity has been performed more than once, I want to change the attribute "total" (By adding +1) for that specific activity, instead of adding a new activity. How can I do this? This is my code so far:
let appDel = (UIApplication.sharedApplication().delegate as! AppDelegate)
let context: NSManagedObjectContext = appDel.managedObjectContext
let request = NSFetchRequest(entityName: "Activities")
request.returnsObjectsAsFaults = false
request.predicate = NSPredicate(format:"(name == %#)", name)
do {
let results = try context.executeFetchRequest(request) as! [Activities]
if results.count == 0 {
newActivity.setValue(1, forKey: "total")
newActivity.setValue(name), forKey: "name")
do {
try context.save()
}
catch {
print(error)
}
}
}
catch let error as NSError {
print(error)
}
}
If you want to update (if I am not mistaken), you can just change the total like this:
results.total = YourWantedNumber or results.setValue(YourWantedNumber, forKey: "total")
context.save()