Fetch Request Error - swift

I've saved a user input name in the previous storyboard into the xcdatamodel under the entity "UserInfo" with the name "name". I'm trying to fetch it in the next storyboard to display in a label to greet the user. I'm getting the error "Cannot Invoke Initializer for type 'NSFetchRequet' with an Argument with list of type '(entityName:String, attributeName: String)"
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
//getting the managed context where the entity we need is
let managedContext = appDelegate.persistentContainer.viewContext
//make fetch request
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "UserInfo", attributeName: "name")
//try to fetch the entity we need, else print error
do {
Username = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}

There is no initializer for NSFetchRequest that takes the argument attributeName:, just like the error says. Your options are NSFetchRequest(entityName:) or NSFetchRequest().
When in doubt, look up the class in the API reference to make sure you understand how to use it.

NSFetchRequest doesn't have an initializer entityName:attributeName, you have to use
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "UserInfo")
fetch returns always an array. If there is only one record in the entity, get the first item and the value for attribute name:
do {
let users = try managedContext.fetch(fetchRequest)
if let user = users.first {
Username = user.value(forKey: "name")
}
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
If there are multiple records you might apply a predicate.
And don't guard AppDelegate. If this class didn't exist the app won't even launch. The exclamation mark is safe as safe can.
let appDelegate = UIApplication.shared.delegate as! AppDelegate

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?

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.

delete core data entry doesn't work

I try to delete an core data entry using Swift. I also use the fetched results controller for loading the entries. This is my code:
let context = self.fetchedResultsController.managedObjectContext
let fetchRequest = NSFetchRequest(entityName:"Person")
fetchRequest.predicate = NSPredicate(format: "name = '\(item)'")
var error : NSError?
if let results = context.executeFetchRequest(fetchRequest, error:&error),
let managedObject = results.first as? NSManagedObject {
context.deleteObject(managedObject)
}
I don't know why but if this code runs the entry is deleted out of the table but if i restart the app the table includes the task which i've deleted.
This only deletes the object from the managed object context (which is the scratchpad for making changes). To persist anything done in a managed object context to the underlying database you need to save it first:
if let results = context.executeFetchRequest(fetchRequest, error:&error),
let managedObject = results.first as? NSManagedObject {
context.deleteObject(managedObject)
}
let saveError: NSError?
context.save(&saveError)
You need to save.
Swift 1.2
context.save(nil)
Swift 2
do { try context.save() } catch {}

Update all value in one attribute Core Data

I know how to fetch all value from one attribute in Core Data using an array. I just need to press a button and -1 all the value and save it back to the Core Data.
How can I update all the value once in swift?
Thanks.
For swift 3
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Params")
do{
if let fetchResults = try managedContext.fetch(request) as? [NSManagedObject] {
if fetchResults.count != 0 {
for index in 0...fetchResults.count-1 {
let managedObject = fetchResults[index]
managedObject.setValue("-1", forKey: "value")
}
try managedContext.save()
}
}
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
Try following example it might be helpful.Replace you entity name.
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
var context: NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Params")
var params = NSEntityDescription.insertNewObjectForEntityForName("Params", inManagedObjectContext: context) as! NSManagedObject
if let fetchResults = appDel.managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [NSManagedObject] {
if fetchResults.count != 0{
for (var v = 0 ; v < fetchResults.count ; v++) {
var managedObject = fetchResults[v]
println(fetchResults)
managedObject.setValue("-1", forKey: "value")
context.save(nil)
}
}
}
While the question is quiet old, I am writing it for those who wish to do this task in a more optimized manner. According to the documentation,
Batch updates run faster than processing the Core Data entities yourself in code because they operate in the persistent store itself, at the SQL level.
Hence using NSBatchUpdateRequest is the best way to achieve the result. Here's a swift code that could do the job in the given scenario with using an extension to help simplify the code while also taking into account the fact that changes made in batch update are not reflected in the objects currently in memory.
extension NSManagedObjectContext {
/// Executes the given `NSBatchUpdateRequest` and directly merges the changes to bring the given managed object context up to date.
///
/// - Parameter batchUpdateRequest: The `NSBatchUpdateRequest` to execute.
/// - Throws: An error if anything went wrong executing the batch deletion.
public func executeAndMergeChanges(using batchUpdateRequest: NSBatchUpdateRequest) throws {
batchUpdateRequest.resultType = .updatedObjectIDsResultType
let result = try execute(batchUpdateRequest) as? NSBatchUpdateResult
let changes: [AnyHashable: Any] = [NSUpdatedObjectsKey: result?.result as? [NSManagedObjectID] ?? []]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [self])
}
}
class MyCoreDataClass {
...
func updateAllParams() {
let request = NSBatchUpdateRequest(entityName: "Params")
request.propertiesToUpdate = ["value" : NSExpression(forConstantValue: -1)]
do {
try managedObjectContext.executeAndMergeChanges(using: request)
} catch {
print(error)
}
}
}
P.S: You need to be aware that validation rules enforced in data model are not considered while executing a batch update. According to the documentation,
When you use batch updates any validation rules that are a part of the data model are not enforced when the batch update is executed. Therefore, ensure that any changes caused by the batch update will continue to pass the validation rules.

how to save values in core data in swift

I have to apply checkmarks on the contacts received by using address book. When I'm selecting contacts then checkmarks appears but after scrolling it disappears. Secondly, I have to save these selected contacts into core data. Just have a look at that and tell me what I'm doing wrong.What Wrong i'am doing with Core Data.
class LogItem: NSManagedObject {
#NSManaged var section: String
#NSManaged var keys: String
}
Now declare the object of this class like this:
let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: self.managedObjectContext!) as! LogItem
newItem.section = "section Title"
newItem.keys = "keys text"
}
You can fetch the data as follows:
// request using the LogItem entity
let fetchRequest = NSFetchRequest(entityName: "LogItem")
// Execute the fetch request, and cast the results to an array of LogItem objects
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
println(fetchResults[0].section) // prints "section title"
println(fetchResults[0].key) // prints key text
}
Please Make sure that core data is able to save only properties.
According to apple documentation, “The Core Data framework provides
generalized and automated solutions to common tasks associated with
object life-cycle and object graph management, including persistence.”
First, you add CoreData, then go to AppDelegate and you will see this code:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Model_data")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
In: let container . . . you see (name: "Model_data")
This name needs to be same as your model file name.
Then go to your View Controller and add:
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let newItem = Item(context: self.context)
Item is an entity from your model object.
add newItem to an array like:
self.itemArray.append(newItem)
Then, add next logic:
fileprivate func saveContext() {
do {
try context.save()
// if it is table view, you need to reload here. tableView.reloadData()
} catch {
print("Failed to with error: \(error)")
}
}
if loading is problem add this logic:
fileprivate func loadContext() {
let request: NSFetchRequest<Item> = Item.fetchRequest()
do {
itemModel = try context.fetch(request)
} catch {
print("Error in request: \(error)")
}
}
Save and load.