Update an object on core data swift 4 - ios

I have several items of jobs in core data entity whose jobId is -1. I need to fetch all those items and update jobId by proper ids which are in my object that is passed in updateMyJobs method. I haven't extracted NSManagedObject class to work on core data (i.e.- I've checked the entity as Class definition)
Here's my code:
func updateMyJobs(jobId: Int){
managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "DBJobsNearBy")
fetchRequest.predicate = NSPredicate(format: "jobId = '-1'")
let result = try? managedObjectContext.fetch(fetchRequest)
let resultData = result as! [DBJobsNearBy]
for object in resultData {
print(object.jobId)
if object.jobId == -1 {
object.setValue("\(jobId)", forKey: "jobId")
}
}
do {
try managedObjectContext.save()
print("saved!")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
}
I passed some integer value and call the above method to update the item like this.
DatabaseHandler.shared.updateMyJobs(jobId: 222)
Error: Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "jobId"; desired type = NSNumber; given type = Swift._NSContiguousString; value = 222.'
I'm trying to set new jobId to the related object in core data entity. Is it necessary to extract NSManagedObject Class or not. Please someone help me to do this. Thank You.

Please read the error message. It's very clear. In setValue you are passing a String (via String Interpolation) rather than expected Int or NSNumber
object.setValue(jobId, forKey: "jobId")
or
object.setValue(NSNumber(value: jobId), forKey: "jobId")
But the best and recommended way is dot notation
object.jobId = jobId

Update Object to core data
#IBAction func buttonUpdate(_ sender: Any) {
let entity = NSEntityDescription.entity(forEntityName: "Students", in: managedContext)
let request = NSFetchRequest<NSFetchRequestResult>()
request.entity = entity
let newName = UserDefaults.standard.value(forKey: "new") as! String
let predicate = NSPredicate(format: "(name = %#)", newName)
request.predicate = predicate
do {
let 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()
labelStatus.text = "Updated"
}catch let error as NSError {
labelStatus.text = error.localizedFailureReason
}
}
catch let error as NSError {
labelStatus.text = error.localizedFailureReason
}
}

You need to set NSNumber instead of String. Replace this line:
object.setValue("\(jobId)", forKey: "jobId")
with:
object.jobId = jobId

Related

How can you get attribute names from an Entity from CoreData at an iOS app

I am reading data with following code from CoreData but instead of that can we read first attribute names "firstName", "lastName", "age" from CoreData into an array and read their values instead of writing all the names in code.
It is repeated work because they are written in DataModel as well.
loadData() {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Entity")
do {
let result = try context.fetch(fetchRequest)
dump(result)
for data in result as! [NSManagedObject] {
fNames = data.value(forKey: "firstName") as! String
lNames = data.value(forKey: "lastName") as! String
age = data.value(forKey: "age") as! Int
print("first \(fNames), last : \(lNames), last : \(age)")
}
} catch {
print("Could not load data: \(error.localizedDescription)")
}
}
Use the class that Xcode has generated for you that has the same name as the entity name
loadData() {
//Declare fetch request to hold the class you want to fetch
let fetchRequest = NSFetchRequest<Entity>(entityName: "Entity")
do {
let result = try context.fetch(fetchRequest)
dump(result)
for data in result {
// result is now [Entity] so you can access properties directly
// and without casting
let firstName = data.firstName
let lastName = data.lastName
let age = data.age
print("first \(firstName), last : \(lastName), age : \(age)")
}
} catch let error as NSError {
print("Could not load data: \(error.localizedDescription)")
}
}
Try this, access your entity name from NSManagedObject
e.g.
For AppDelegate.SharedInstance(), just declare this func in AppDelegate.swift
class func sharedInstance() -> AppDelegate
{
return UIApplication.shared.delegate as! AppDelegate
}
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName:"CallHistory") //Here CallHistory is my entity name which i can access as NSManagedObject
let arr_callhistory = try AppDelegate.sharedInstance().persistentContainer.viewContext.fetch(fetchRequest) as! [CallHistory]
if arr_callhistory.count != 0
{
for callhistory_dict in arr_callhistory
{
let callStatus = callhistory_dict.callStatus
}
}

Core Data fetching data and looking for duplicate

I want to fetch data from Core data and look for duplicats and then only save the data then there is no duplicate of the movieid.
Maybe some one can help me ..
How can I compare the result with the movieid string ?
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "MovieData")
//request.predicate = NSPredicate(format: "movieid = %#", movieID)
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
print(data.value(forKey: "movieid") as! String)
}
} catch {
print("Failed")
}
Almost. Apply the predicate to get only the record with the specific movieID. However it assumes that movieID is an object (NSNumber), if it's an scalar Int you have to use %ld as placeholder.
If the fetch returns an empty array there is no duplicate and you can insert a new object
let request = NSFetchRequest<NSManagedObject>(entityName: "MovieData")
request.predicate = NSPredicate(format: "movieid = %#", movieID)
do {
let result = try context.fetch(request)
if result.isEmpty {
let newMovie = NSEntityDescription.insertNewObject(forEntityName: "MovieData", into: context) as! MovieData
newMovie.movieid = movieID
try context.save()
}
} catch {
print(error)
}
While saving in core data you need to create predicate and in there you need to check if there are values already saved with same "movieid" then it has to be updated , this way you won't have duplicate data . Please refer the method and try using the same for saving the values in DB . This way duplicate values won't be saved in DB
class func insertupdaterecord (movieID:String, context: NSManagedObjectContext)
{
let entityDescription = NSEntityDescription.entity(forEntityName: "movie", in: context)
let pred = NSPredicate(format: "movieid = %#", movieID)
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "movie")
fetchRequest.entity = entityDescription
fetchRequest.predicate = pred
let result = try! (context.fetch(fetchRequest) as NSArray).lastObject
let updateInsertInfo : movie
if result != nil
{
updateInsertInfo = result as! movie
}
else
{
print("Record not found!")
}
do
{
try context.save()
}
catch let error as NSError
{
print("Error while saving \(error) in database.")
}
}
Create a cache for movieid values to check for duplicates and loop through the fetched result and delete any objects with a movieid already in the cache and then save once the loop is done.
var selection: [String] = []
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "MovieData")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
guard let movieId = data.value(forKey: "movieid") as? String else {
context.delete(data) // or however you want to handle this situation
continue
}
if selection.contains(movieId) {
context.delete(data)
} else {
selection.append(movieId)
}
}
try context.save()
} catch {
print("Failed")
}

Core Data in iOS, index out of range

In my App I am using Core Data and up to this point everything works great. I am having a problem when I try to update an entry. What is interesting is that I can update everything in the entity except for the address field. I don't do anything different but the app crashes when I try to update only that field with a fatal error: Index out of range. Here is the screen shot of the actual entity.
It is just some client information and all entries are strings. In my edit vc I have used a var to hold the client info, set the delegate and used some predicates. Here is the code:
//This is declared right under the class
var myClientToEdit: NSManagedObject = NSManagedObject()
//my save button action
#IBAction func saveMyEdits(_ sender: Any)
{
//get the delegate
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else
{
return
}
//get managedContext
let managedContext = appDelegate.persistentContainer.viewContext
//get the client's entry
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Clients")
fetchRequest.returnsObjectsAsFaults = false
//use predicates to make sure it is the client I need
fetchRequest.predicate = NSPredicate(format: "lastName == %#", mylastName)
fetchRequest.predicate = NSPredicate(format: "firstName == %#", myfirstName)
fetchRequest.predicate = NSPredicate(format: "address = %#", myaddress)
do {
var myClientToEdit = try managedContext.fetch(fetchRequest)
let myObject = myClientToEdit[0]
//my correct client is printed
print(myObject)
//using new entries to set new values
myObject.setValue(lastName.text!, forKey: "lastName")
myObject.setValue(firstName.text!, forKey: "firstName")
myObject.setValue(address.text!, forKey: "address")
myObject.setValue(city.text!, forKey: "city")
myObject.setValue(state.text!, forKey: "state")
myObject.setValue(zip.text!, forKey: "zipCode")
myObject.setValue(phoneDay.text!, forKey: "phoneDay")
myObject.setValue(phoneEve.text!, forKey: "phoneEvening")
myObject.setValue(email.text!, forKey: "email")
do{
//save
try managedContext.save()
}catch let error as NSError{
print(error)
}
}catch let error as NSError {
print(error)
}
//dismis upon saving edits
self.dismiss(animated: true, completion: nil)
}
When I come back to my detail client vc I do the same thing but just read from Core Data. Here is the code for the detailed client vc.
override func viewWillAppear(_ animated: Bool)
{
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else
{
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Clients")
fetchRequest.predicate = NSPredicate(format: "lastName == %#", myLastName)
fetchRequest.predicate = NSPredicate(format: "firstName == %#", myFirstName)
fetchRequest.predicate = NSPredicate(format: "address == %#", myAddress)
do {
var thisIsTheClient = try managedContext.fetch(fetchRequest)
let myObject = thisIsTheClient[0]
print(myObject)
lastNameLabel.text = myObject.value(forKey: "lastName") as? String
firstNameLabel.text = myObject.value(forKey: "firstName") as? String
myCityLabel.text = myObject.value(forKey: "city") as? String
myAddressLabel.text = myObject.value(forKey: "address") as? String
myStateLabel.text = myObject.value(forKey: "state") as? String
myZipLabel.text = myObject.value(forKey: "zipCode") as? String
myDayLabel.text = myObject.value(forKey: "phoneDay") as? String
myEveLabel.text = myObject.value(forKey: "phoneEvening") as? String
myEmailLabel.text = myObject.value(forKey: "email") as? String
}catch let error as NSError
{
print(error)
}
}
The error is on the line: let myObject = thisIsTheClient[0]. BUT only when I try and edit the address. thisIsTheClient is defined under the class as well. I know index out of range has something to do with trying to put something where there is nothing like in an array, but I don't understand why it only does that when I try and edit the address. All other entries are updated perfectly.
EDIT 7/19/17
Thanks for everyone's responses! Both Maor, and Jay had the predicate thing right. I was only searching for the address on the client, not the first three attributes. And after Jay's note about the var, I was able to pass the new data back and upadate the var and search for the new record. Thanks again!
I think you have a different problem than what you think it is.
First, when you write:
fetchRequest.predicate = NSPredicate(format: "lastName == %#", myLastName)
fetchRequest.predicate = NSPredicate(format: "firstName == %#", myFirstName)
fetchRequest.predicate = NSPredicate(format: "address == %#", myAddress)
Only the last line is taking into account since it is overrides whatever comes before it, which means that in your case it will only look for the address attribute (and I guess this why you believed that your problem is only in the address field..)
You should follow what #Jay offered you to do to have a compound predicates.
The second thing (if I understand correctly), is that you changed the address to whatever text myAddressLabel.text holds, and saved it. However when you read from core data on detail vc, you still try to query by "myAdress" variable, which is different than the one you saved. And if that is the case than it explains why you get this error message- it is simply didn't find anything matches your search, so when you try to get the first element out of an empty array it tells you that it is out of bounds...
I'm fairly sure in order to combine predicates you have to use a compound predicate, instead of just trying to change the property of the fetch request:
let predicate1:NSPredicate = NSPredicate("label = 'foo'")
let predicate2:NSPredicate = NSPredicate("label = 'bar'")
let compound:NSCompoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate1,predicate2])

Core Data Value returns Nil

Hi i have been working with core data to store and retrieve some values(String only) from the core data. Here is how i am storing the values.
The Function :
public func saveStringValue(forKey: String, value: String) -> Bool{
var saved = false
if self.entityName != nil && self.appDelegate != nil{
let context = appDelegate?.persistentContainer.viewContext
if context != nil{
let entity = NSEntityDescription.entity(forEntityName: self.entityName!, in: context!)
let entityHandle = NSManagedObject(entity: entity!, insertInto: context!)
entityHandle.setValue(value, forKey: forKey)
do{
try context?.save()
saved = true
}catch let error as NSError{
saved = false
print("Error : \(error)")
}
}
}
return saved
}
Here is how i call it
let historyManager = HistoryManager(entity: "SearchHistory")
let titleInserted = historyManager.saveStringValue(forKey: "title", value: book.title!)
if(titleInserted == true)
{
print("Title Inserted to Entity")
}
if let image = book.imageUrl{
let imageInserted = historyManager.saveStringValue(forKey: "image", value: image)
if imageInserted == true{
print("Image Url Inserted to Entity")
}
}
I can see in the console printed that
Title inserted into entity
ImageInserted Into entity
Here is the code to retrieve the value from core data store
public func fetchAll() -> [Book]{
var books = [Book]()
let context = self.appDelegate?.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: self.entityName!)
//let fetchRequest: NSFetchRequest<SearchHistory> = SearchHistory.fetchRequest()
do{
let fetchedBooks = try context?.fetch(fetchRequest)
for aBook in fetchedBooks!{
if let title = aBook.value(forKey: "title"){
let book = Book(title: title as! String)
if let im = aBook.value(forKey: "image"){
book.imageUrl = im as! String
print("ImageUrl : \(im) : ")
}
else{
print("No Value for key : image")
}
books.append(book)
}
}
}
catch let error as NSError{
print("Fetch Error: \(error.localizedDescription)")
}
print("Books : \(books.count)")
return books
}
But when i run the code to retrieve the book imageUrl it returns nil and prints
No value for key : image
It retrieves the title but not the imageUrl.
Can you help me through this problem or point me to the right direction. And please do post the reason why i was getting this problem and how to solve it. Thanks.
Your problem is that your saveStringValue creates a new NSManagedObject instance each time you call it.
The first time you call saveStringValue you will create a SearchHistory object that has a title but no image. The second time you call it you will create another SearchHistory object with an image value but no title.
In my opinion, your saveStringValue function is unnecessary. Assuming your code is based on a template that resulted from clicking "use Core Data" in Xcode, you will have a SearchHistory class available and you can use something like this:
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let newHistory = SearchHistory(context: context)
newHistory.title = book.title
newHistory.image = book.imageUrl
appDelegate.saveContext()

Update core data object swift 3

I want to update a core data object in swift 3. After some googled I didn't found anything about swift 3.
So my question is: how can I update a core data object in swift 3?
Fetch the existing values using a fetch request with a predicate. Use a unique value in the predicate. Once you've fetched the object, update the object with new values and save the context.
let empId = "001"
let fetchRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "EmpDetails")
let predicate = NSPredicate(format: "empId = '\(empId)'")
fetchRequest.predicate = predicate
do {
let result = try persistentContainer.viewContext.fetch(fetchRequest)
if let objectToUpdate = result.first as? NSManagedObject {
objectToUpdate.setValue("newName", forKey: "name")
objectToUpdate.setValue("newDepartment", forKey: "department")
objectToUpdate.setValue("001", forKey: "empID")
try persistentContainer.viewContext.save()
}
} catch {
print(error)
}
Using NSManagedObject subclass
let empId = "001"
let fetchRequest: NSFetchRequest<Employee> = Employee.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "%K = %#", #keyPath(Employee.id), empId)
do {
let results = try persistentContainer.viewContext.fetch(fetchRequest)
if let employee = results.first {
employee.name = "new name"
employee.department = "new department"
}
try persistentContainer.viewContext.save()
} catch let error as NSError {
print(error.localizedDescription)
}
Batch updates
Batch updates help to update multiple Core Data objects without having
to fetch anything into memory.
let batchUpdate = NSBatchUpdateRequest(entityName: "Employee")
batchUpdate.propertiesToUpdate = [#keyPath(Employee.isActive): true]
batchUpdate.affectedStores = persistentContainer.viewContext.persistentStoreCoordinator?.persistentStores
batchUpdate.resultType = .updatedObjectsCountResultType
do {
let batchResult = try coreDataStack.managedContext.execute(batchUpdate) as? NSBatchUpdateResult
print(batchResult?.result)
} catch let error as NSError {
print(error.localizedDescription)
}
Pass unique id in variable "id"(Unique variable created in Core data model) and all the variable as you want to update values:
func context() -> NSManagedObjectContext {
let context=(UIApplication.shared.delegate as!AppDelegate).persistentContainer.viewContext
return context
}
func save() {
(UIApplication.shared.delegate as! AppDelegate).saveContext()
}
func UpdateCartByTestId(id:Int64,name:String) {
let fetchRequest =
NSFetchRequest<NSManagedObject>(entityName: "Update")
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.predicate = NSPredicate(format:"id == %d",id)
let result = try? context().fetch(fetchRequest)
if result?.count == 1 {
let dic = result![0]
dic.setValue(id, forKey: "id")
dic.setValue(name, forKey: "name")
save()
}
}

Resources