I have been struggling with creating a class to manage Core Data.
I cannot seem to get passed the "Failed to call designated initializer on NSManaged Object"
How can I fix this?
This is my created class to manage the data :
import Foundation
import CoreData
import UIKit
class Hours: NSManagedObject {
#NSManaged var date: NSDate
#NSManaged var startTime: NSDate
#NSManaged var endTime: NSDate
#NSManaged var totalTime: Double
override init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?){
let entity = NSEntityDescription.entityForName("Hours", inManagedObjectContext: context!)
super.init(entity: entity!, insertIntoManagedObjectContext: context)
}
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var context:NSManagedObjectContext {
get{
return appDel.managedObjectContext
}
}
class func insertTimes(date: NSDate, startTime:NSDate, endTime:NSDate,totalTime:Double) -> Hours{
let hour:Hours = Hours()
let newDate:Hours = NSEntityDescription.insertNewObjectForEntityForName("Hours", inManagedObjectContext: hour.context) as! Hours
print("date = \(date), start = \(startTime), end = \(endTime), total = \(totalTime)")
newDate.date = date
newDate.startTime = startTime
newDate.endTime = endTime
newDate.totalTime = totalTime
/*
newDate.setValue(date, forKey: "date")
newDate.setValue(startTime, forKey: "startTime")
newDate.setValue(endTime, forKey:"endTime")
newDate.setValue(totalTime, forKey: "totalTime")
*/
do{
try hour.context.save()
print("Succesfully saved")
}catch{
print("Unresolved save error")
}
return newDate
}
func returnDate(){
//Does not work YET
let request = NSFetchRequest(entityName: "Hours")
//request.returnsObjectsAsFaults = false
do{
let results = try context.executeFetchRequest(request)
print(results)
}catch{
print("Unresolved fetch error")
}
}
}
I try to call the method insertTimes with the following line of code, after I press a button.
#IBAction func saveData(sender: AnyObject) {
let inserted:Hours = Hours.insertTimes(date, startTime: startEndDate.startTime, endTime: startEndDate.endTime, totalTime: timeInterval)
}
Please note that all my variables are of the right types.
Source can be found on: https://github.com/bbriann123/PayDay/blob/master/Hours.swift
Look a these:
In AppDelegate you use "Loon Calculator" instead of "DayDataModel": https://github.com/bbriann123/PayDay/blob/master/Loon%20Calculator/AppDelegate.swift#L55 (the data model file name is clearly "DayDataModel": https://github.com/bbriann123/PayDay/tree/master/Loon%20Calculator/DayDataModel.xcdatamodeld/DayDataModel.xcdatamodel)
In the data model you don't specify "Hours" as the custom class of the "Hours" entity (look in the Core Data inspector)
Also, it's weird how you reach to the NSManagedObjectContext: https://github.com/bbriann123/PayDay/blob/master/Hours.swift#L25-L30
Maybe you need a better Core Data book (or whatever you're using to learn)?
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
}
Here's my medicine object class and extension:
import Foundation
import CoreData
class Medicine: NSManagedObject {
#NSManaged var alarms: NSSet
}
-
import Foundation
import CoreData
extension Medicine {
#NSManaged var name: String?
#NSManaged var dosage: String?
#NSManaged var type: String?
#NSManaged var image: NSData?
func addAlarmObject(value:Alarm) {
let items = self.mutableSetValueForKey("alarms")
items.addObject(value)
}
func removeDeleteObject(value:Alarm) {
let items = self.mutableSetValueForKey("alarms")
items.removeObject(value)
}
}
My alarm object:
import Foundation
import CoreData
extension Alarm {
#NSManaged var time: String?
#NSManaged var weekdays: String?
#NSManaged var isOwnedByMedicine: Medicine?
}
Screenshot of my data models and their relationship:
FINALLY, here is what I'm trying to do:
let predicate = NSPredicate(format: "name == %#", currentMedicine)
let fetchRequest = NSFetchRequest(entityName: "Medicine")
fetchRequest.predicate = predicate
var fetchedCurrentMedicine:Medicine!
do {
let fetchedMedicines = try managedContext.executeFetchRequest(fetchRequest) as! [Medicine]
fetchedCurrentMedicine = fetchedMedicines.first
} catch {
}
//add all the alarms to the Medicine class
for alarmString in alarmList{
let entity = NSEntityDescription.entityForName("Alarm", inManagedObjectContext: managedContext)
let alarm = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedContext) as! Alarm
alarm.setValue("test", forKey: "weekdays")
alarm.setValue(String(alarmString), forKey: "time")
fetchedCurrentMedicine.addAlarmObject(alarm)
}
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
It keeps failing at
fetchedCurrentMedicine.addAlarmObject(alarm)
and I get the error:
"'NSInvalidArgumentException', reason: 'NSManagedObjects of entity 'Medicine' do not support -mutableSetValueForKey: for the property 'alarms''"
Any idea where I may have messed up or gotten the schema of my data models wrong? Much appreciated.
On your data model screen alarms relation of Medicine model is to one. Perhaps medicines relation of Alarm model is to-many.
Anyway try to set to-many relation type for alarms in the data model inspector.
It's the first time I'm trying to save and retrieve custom data to/from my core data, but I've run into an error saying:
fatal error: array cannot be bridged from Objective-C
when I try to load the data back.
My code looks like this, the arrayOfNames is declared as [String]:
#IBAction func saveTap(sender: AnyObject) {
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let contxt: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("Indexes", inManagedObjectContext: contxt)
let arrayData: NSData = NSKeyedArchiver.archivedDataWithRootObject(arrayOfNames)
let newIndex = Indexes(entity: en!, insertIntoManagedObjectContext: contxt)
newIndex.monday = arrayData
println(newIndex.monday)
contxt.save(nil)
}
#IBAction func loadTap(sender: AnyObject) {
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
let fetchReq = NSFetchRequest(entityName: "Indexes")
let en = NSEntityDescription.entityForName("Indexes", inManagedObjectContext: context)
var myList:[String] = context.executeFetchRequest(fetchReq, error: nil) as! [String]
println(myList)
}
My model-file looks like this:
#objc(Indexes)
class Indexes: NSManagedObject {
#NSManaged var monday: NSData
#NSManaged var tuesday: NSData
#NSManaged var wednesday: NSData
#NSManaged var thursday: NSData
#NSManaged var friday: NSData
}
I've also set all the attributes to transformable in my data model. As I said, it's the first time I'm doing this, so sorry if the solution is obvious.
Any suggestions would be appreciated.
executeFetchRequest returns [AnyObject]?, not [String], indeed the objects in the array should be Indexes instances which contain your archived arrays of strings.
So, you need to correct the array type that the results of the fetch are being placed into.
Core Data works great for the most part. When I click on name first VC (Items) and performSeque to the second VC (Costs), I can see the costsName and other data. But when I add second name in first VC I can see the same data as in first name.
I'm trying to make a one to many relationship.
I have 2 data models:
import Foundation
import CoreData
#objc(Items)
class Items: NSManagedObject {
#NSManaged var count: NSNumber
#NSManaged var name: String
#NSManaged var cost: NSSet
}
import Foundation
import CoreData
#objc(Costs)
class Costs: NSManagedObject {
#NSManaged var costsDate: NSDate
#NSManaged var costsName: String
#NSManaged var costsValue: NSNumber
#NSManaged var account: Items
}
Here is addAccount's (name of the first VC) save action:
#IBAction func saveButtonPressed(sender: UIBarButtonItem) {
let appDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var managedObjectContext = appDelegate.managedObjectContext
let entityDescription = NSEntityDescription.entityForName("Items", inManagedObjectContext: managedObjectContext!)
let account = Items(entity: entityDescription!, insertIntoManagedObjectContext: managedObjectContext!)
account.name = cellOneNameTextField.text
if cellTwoCountTextField.text.isEmpty {
} else {
account.count = (cellTwoCountTextField.text).toInt()!
}
// Saving data
appDelegate.saveContext()
var request = NSFetchRequest(entityName: "Items")
var error:NSError? = nil
var results:NSArray = managedObjectContext!.executeFetchRequest(request, error: &error)!
self.navigationController?.popViewControllerAnimated(true)
}
Here is addCost's save action:
#IBAction func saveButtonTapped(sender: UIBarButtonItem) {
// CoreData Access
let appDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var managedObjectContext = appDelegate.managedObjectContext
let entityDescription = NSEntityDescription.entityForName("Costs", inManagedObjectContext: managedObjectContext!)
let cost = Costs(entity: entityDescription!, insertIntoManagedObjectContext: managedObjectContext!)
cost.costsName = cellThreeNoteTextField.text
cost.costsValue = (cellOnePriceTextField.text).toInt()!
cost.costsDate = datePicker.date
// Saving data
appDelegate.saveContext()
var request = NSFetchRequest(entityName: "Costs")
var error:NSError? = nil
var results:NSArray = managedObjectContext!.executeFetchRequest(request, error: &error)!
for res in results {
println(res)
}
delegate?.refreshTable()
self.navigationController?.popViewControllerAnimated(true)
}
I don't know if you do it somewhere, but your Items should attach a Count object to itself using your cost variable from your Items. Something like :
let account = Items(...)
let cost = Cost(...)
account.cost.addObject(cost)//and changing your var cost:NSSet into var cost:NSMutableSet
//then save Items
(I haven't tried the addObject but you understand the principle)
I'm an iOS newbie developer. I'm trying to import the results of an executeFetchRequest into a structure I created for viewing later into a table. I"m getting "Array index out of range" in func getTasks(), I'm pretty sure I"m supposed to append it, but not sure quite how.
I'm sure there's a better way of setting this up in general. Right now I'm just trying to get things to work. But other suggestions would be appreciated.
import UIKit
import CoreData
var taskMgr: TaskManager = TaskManager()
struct task {
var name = "Un-Named"
var desc = "Un-Described"
}
class TaskManager: NSObject {
var tasks = task[]()
init() {
super.init()
self.getTasks()
}
func addTask(name: String, desc: String) {
tasks.append(task(name: name, desc: desc))
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext
let ent = NSEntityDescription.entityForName("Tasks", inManagedObjectContext: context)
var newTask = Tasks(entity: ent, insertIntoManagedObjectContext: context)
newTask.name = name
newTask.desc = desc
println("Object saved")
context.save(nil)
}
func getTasks() {
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext
var request = NSFetchRequest(entityName: "Tasks")
request.returnsObjectsAsFaults = false;
var results:NSArray = context.executeFetchRequest(request, error: nil)
if (results.count > 0) {
self.tasks = task[]()
var i = 0
for element in results {
tasks[i].name = element.name // fatal error: Array index out of range
tasks[i].desc = element.desc
i++
}
}
}
}
class Tasks: NSManagedObject {
#NSManaged var name: String
#NSManaged var desc: String
}
You can't use subscripting to add items to an array -- you need to call append() or use the += operator instead. Try this:
self.tasks = task[]()
for element in results {
tasks += task(name: element.name, desc: element.desc)
}