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])
Related
I'm working on iOS application(Swift) which is using core data. In this application I use an object called Audit and once audit has completed I updated the active boolean property of that object to false. Following is the Audit object saving method. I used same method to both create and update cases.
class func saveAudit(audit:AuditModel){
let curntAudit = Utilities.auditDetails(auditId: Int(audit.auditId))
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {return }
let managedContext = appDelegate.persistentContainer.viewContext
if curntAudit != nil {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "AuditModel")
fetchRequest.predicate = NSPredicate(format: "auditId == %i", curntAudit!.auditId)
fetchRequest.returnsObjectsAsFaults = false
do {
let results = try managedContext.fetch(fetchRequest) as? [AuditModel]
if results!.count > 0 {
var auditModl = results?.first
auditModl!.date = audit.date
auditModl!.active = audit.active
auditModl!.critical = audit.critical
auditModl!.isNew = audit.isNew
auditModl!.pDepartments = audit.pDepartments
auditModl!.pImages = audit.pImages
auditModl!.sended = audit.sended
auditModl!.showNa = audit.showNa
auditModl!.abbreveation = audit.abbreveation
auditModl!.languages = audit.languages
auditModl!.templateId = audit.templateId
auditModl!.title = audit.title
auditModl!.conclusions = audit.conclusions
auditModl!.positiveAspects = audit.positiveAspects
auditModl!.idControl = audit.idControl
auditModl!.idDashboard = audit.idDashboard
auditModl!.locationId = audit.locationId
auditModl!.styleId = audit.styleId
auditModl!.totalComments = audit.styleId
auditModl!.totalImages = audit.styleId
auditModl!.totalQuestions = audit.styleId
auditModl!.majorAspects = audit.majorAspects
}
} catch {
print("failed")
}
} else {
let auditEntity = NSEntityDescription.entity(forEntityName: "AuditModel", in: managedContext)
let auditModl = NSManagedObject(entity: auditEntity!, insertInto: managedContext)
auditModl.setValue(audit.date, forKey: "date")
auditModl.setValue(audit.active, forKey: "active")
auditModl.setValue(audit.critical, forKey: "critical")
auditModl.setValue(audit.isNew, forKey: "isNew")
auditModl.setValue(audit.pDepartments, forKey: "pDepartments")
auditModl.setValue(audit.pImages, forKey: "pImages")
auditModl.setValue(audit.sended, forKey: "sended")
auditModl.setValue(audit.showNa, forKey: "showNa")
auditModl.setValue(audit.abbreveation, forKey: "abbreveation")
auditModl.setValue(audit.languages, forKey: "languages")
auditModl.setValue(audit.templateId, forKey: "templateId")
auditModl.setValue(audit.title, forKey: "title")
auditModl.setValue(audit.conclusions, forKey: "conclusions")
auditModl.setValue(audit.auditId, forKey: "auditId")
auditModl.setValue(audit.positiveAspects, forKey: "positiveAspects")
auditModl.setValue(audit.idControl, forKey: "idControl")
auditModl.setValue(audit.idDashboard, forKey: "idDashboard")
auditModl.setValue(audit.locationId, forKey: "locationId")
auditModl.setValue(audit.styleId, forKey: "styleId")
auditModl.setValue(audit.totalComments, forKey: "totalComments")
auditModl.setValue(audit.totalImages, forKey: "totalImages")
auditModl.setValue(audit.totalQuestions, forKey: "totalQuestions")
auditModl.setValue(audit.majorAspects, forKey: "majorAspects")
}
do {
try managedContext.save()
} catch let error as NSError {
print("Couldn't save. \(error), \(error.userInfo)")
}
}
Right after updating the active flag, User will redirect to the separated screen and that screen will shows the list of all the audit objects which is active == false. Following is the data retrieving method.
class func finishedAudits() -> [AuditModel]? {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {return nil}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "AuditModel")
fetchRequest.predicate = NSPredicate(format: "active == NO")
fetchRequest.returnsObjectsAsFaults = false
do {
let results = try managedContext.fetch(fetchRequest)
return results as! [AuditModel]
} catch {
print("failed")
return nil
}
}
Problem is the recently updated object is not retrieve by the above method. Once I re-run the application then only above method shows the recently updated object. I have tried to fix this different ways but non of them were working. Is anybody knows what's wrong here? creating managedContext object in every method causes to this problem?
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {return }
let managedContext = appDelegate.persistentContainer.viewContext
Can anyone help me with this? I'm stuck with this since few days now.
Thank you in advanced.
Fetch requests only fire once. They are not updated dynamically when objects are inserted.
Use a breakpoint to see if your fetch method is actually being run before the insert is performed, possibly by some other action or method, or if it is not actually run again after the insert.
To simply this process, I would recommend using a FetchedResultsController which monitors the database for changes that would be returned by its corresponding fetch request. See https://developer.apple.com/documentation/coredata/nsfetchedresultscontroller
How can I fetch the CoreData value something like in SQL SELECT * FROM user where username="something"
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let db = appDelegate.persistentContainer.viewContext
let request : NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "User")
let predicate = NSPredicate(format: "username='\(usernameLabel.text!)'")
request.predicate = predicate
do{
let result = try db.fetch(request) as! [NSManagedObject]
var get_email : String = ""
for item in result {
if let email = (item as AnyObject).value(forKey: "email") as? String {
get_email.append(email)
}
}
emailTextField.text = get_email
}
catch let error as NSError{
print(error)
}
in above code, the output will be something like tester1#tester.comtester2#tester.com It actually fetch entire email column.
Your predicate looks fine (well, you'd be better using placeholders rather than string interpolation) so the most likely problem is that the username field isn't unique.
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
I want to search a string in my DB and it keeps returning all records from core data. The other questions I saw here didn't work. Suggestions?
var users:[Users] = []
override func viewDidLoad() {
super.viewDidLoad()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let myrequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Users")
let predicate = NSPredicate(format: "name = '%#'", "alex")
myrequest.predicate = predicate
do{
users = try context.fetch(myrequest) as! [Users]
}catch{
print("error")
}
}
it's working now. this is the final code. Maybe it will help someone in the future :) thanks for the help guys.
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let query:NSFetchRequest<Users> = Users.fetchRequest()
let key = "alex"
let predicate = NSPredicate(format: "name contains[c] %#", key)
query.predicate = predicate
do{
users = try context.fetch(query)
print(users.count)
}catch{
print("error")
}
You should not use single quotes in your predicate. Try with this:
let predicate = NSPredicate(format: "name = %#", "alex")
myrequest.predicate = predicate
try to do this in do case
users = try context.fetch(Users.fetchRequest())
I'm developing an app to store one phone number at time using core data, the user should be able to enter a new phone number into ui text field,if it's equal to nil, it should store a new phone number,else it should replace old number with new number; it should store only one value.
but the code doesn't work as it should
what's wrong in my code?
let moContext: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = moContext.managedObjectContext
let phoneNu = NSEntityDescription.insertNewObjectForEntityForName("Setting", inManagedObjectContext: context)
let fetchRequest = NSFetchRequest(entityName: "Setting")
fetchRequest.predicate = NSPredicate(format: "phoneNumber = %#", phoneNumber)
// phoneNu.setValue(phoneNumber.text, forKey: "phoneNumber")
do{
let request = NSFetchRequest(entityName: "Setting")
let phoneN = try context.executeFetchRequest(request)
if phoneN.count == 0{
phoneNu.setValue(phoneNumber.text, forKey: "phoneNumber")
}else if phoneN.count != 0{
for item in phoneN as! [NSManagedObject]{
let number = item.valueForKey("phoneNumber")
number?[0]?.setValue(phoneNumber.text, forKey: "phoneNumber")
}
}
}catch{
print("error")
}
do{
let request = NSFetchRequest(entityName: "Setting")
let phoneNumber = try context.executeFetchRequest(request)
if phoneNumber.count > 0{
for item in phoneNumber as! [NSManagedObject]{
let number = item.valueForKey("phoneNumber")
print(number!)
}
}
}catch{
}
This is my solution
let moContext: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = moContext.managedObjectContext
guard let phoneNumber = phoneNumber.text where phoneNumber.characters.count > 0 else {
return// returns if textField is empty.
}
let fetchRequest = NSFetchRequest(entityName: "Setting")
fetchRequest.predicate = NSPredicate(format: "phoneNumber = %#", phoneNumber)
do{
let request = NSFetchRequest(entityName: "Setting")
let phoneObjectArray = try context.executeFetchRequest(request)
guard let settingFetched = phoneObjectArray as? [Setting] where settingFetched.count > 0 else {
// if enters here -> 0 phone numbers were found
if let setting = NSEntityDescription.insertNewObjectForEntityForName("Setting", inManagedObjectContext: context) as? Setting {
setting.phoneNumber = phoneNumber
// call context.save()
}
return // this I forgot
}
let settingObject = settingFetched.first
// fetch results return store phone number.
// update phone number
settingObject.phoneNumber = phoneNumber
// call context.save()
} catch let error as NSError! {
print("error: \(error)")
}
You need to call the "save()" function to persist the information.
I did some changes to the name variables, added some validations and assume some model class name based on your code.
I always recommend to use your NSManagedObject subclass (Setting I assume) instead of just using NSManagedObject and key/value coding.
There could be some minor syntax mistakes on the code because I was using a text editor.
Hope this helps!
For what you want to do, Core Data is overkill.
Use NSUserDefaults instead. So much easier.