Problem
I'm trying to simply save a record and then fetch it but I think I'm doing something wrong here as my record isn't saving. The output window just shows an empty array.
I'm using the boiler-plate AppDelegate CoreData stack in Xcode 7 beta 5. [See Gist Here]
Attempt
Entity
Model
import Foundation
import CoreData
// #func(Person) edit: removed
class Person: NSManagedObject {
#NSManaged var firstName: String?
#NSManaged var lastName: String?
}
View Controller
import UIKit
import CoreData
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
seedPerson()
fetch()
}
func seedPerson() {
let managedContext = AppDelegate().managedObjectContext
let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext: managedContext)
let person = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedContext)
// add our data
person.setValue("Dan", forKey: "firstName")
person.setValue("Beaulieu", forKey: "lastName")
// save it
do {
try AppDelegate().managedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
func fetch() {
let moc = AppDelegate().managedObjectContext
let personFetch = NSFetchRequest(entityName: "Person")
do {
let fetchedPerson = try moc.executeFetchRequest(personFetch) as! [Person]
print(fetchedPerson.first!.firstName)
} catch {
fatalError("Failed to fetch person: \(error)")
}
}
}
Desired Behavior
What I'm trying to return is my first name from the data store, however I don't think my record is ever saved which is why I'm getting is an empty array.
So my specific question is, how do I save using the boiler plate core data stack?
The problem was that I was creating a new instance of managedObjectContext and trying to save to it which obviously wouldn't work because I was working with the managed context that I created at the top of my method.
func seedPerson() {
let managedContext = AppDelegate().managedObjectContext
//let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext: managedContext)
//let person = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedContext)
let person = NSEntityDescription.insertNewObjectForEntityForName("Person", inManagedObjectContext: managedContext)
// add our data
person.setValue("Dan", forKey: "firstName")
person.setValue("Beaulieu", forKey: "lastName")
// save it
do {
// this was the problem ///////////////
try managedContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
Video
I've uploaded a video tutorial on how to setup core data in swift 2 : https://www.youtube.com/watch?v=WcQkBYu86h8
Related
I'm making a game that has a few constants that are in it:
struct moneyConstants {
static var tapValue = NSInteger()
static var tapMultiplier = NSInteger()
static var moneyPerSecond = NSInteger()
static var money = NSInteger()
}
I'm using CoreData to save the values after when the application closes. I want to know if you are able to save multiple values at once. Lets say my code to save something is:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let entity = NSEntityDescription.entityForName("Constants", inManagedObjectContext:managedContext)
let moneyS = NSManagedObject(entity: entity!, insertIntoManagedObjectContext:managedContext)
moneyS.setValue(moneyConstants.money, forKey: "moneySave")
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
If I wanted to save a second value like:
let moneyPerSecondS = NSManagedObject(entity:entity!, insertIntoManagedObjectContext:managedContext)
moneyPerSecondS.setValue(moneyConstants.moneyPerSecond, forKey: "money")
Can I place those lines before the do { section? Or do I have to put it after the do { section and then write another do { section?
Of course you can (if the attributes are declared in the model):
let moneyS = NSManagedObject(entity: entity!, insertIntoManagedObjectContext:managedContext)
moneyS.setValue(moneyConstants.money, forKey: "moneySave")
moneyS.setValue(moneyConstants.moneyPerSecond, forKey: "money")
Everything which does not throw is not required to be placed in a do scope.
Hello I am having an issue with core data in Swift, this doesn't show an error but also doesn't print anything when I believe it should be returning 'Joe' + 'pass' in console. Could anyone help please?
import UIKit
import CoreData
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var appDel:AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var context:NSManagedObjectContext = appDel.managedObjectContext
var newUser = NSEntityDescription.insertNewObjectForEntityForName("Users", inManagedObjectContext: context) as NSManagedObject
newUser.setValue("Joe", forKey: "username")
newUser.setValue("pass", forKey: "password")
do {
try context.save()
} catch let error {
print("Couldn't save user data")
print(error)
}
let request = NSFetchRequest(entityName: "Users")
request.returnsObjectsAsFaults = false
do {
var results = try context.executeFetchRequest(request)
results = results as! [NSManagedObject]
if results.count > 0 {
for results in results {
print(results)
}}
} catch let error as NSError {
print(error)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You should create your own coredata class (as a subclass of NSManagedObject) and then create the array out of that class.
specify exactly what you want to print. In your case
newUser.username
I finally realised what my issue was, I hadn't actually renamed by Entity to 'Users' I'd kept it as 'Entity' so when I was doing
let newUser = NSEntityDescription.insertNewObjectForEntityForName("Users", inManagedObjectContext: context) as NSManagedObject
let request = NSFetchRequest(entityName: "Users")
I should have actually have replaced both Users with 'Entity' which now fixes my issue, thanks I guess for your help anyway!
I'm trying to add data to Core Data, which I think i'm on the right track (I'm fairly new to this so, apologies) From the code below, my 'Aim' is to check the Core Data and if it's empty add the default data.
Code:
class func musicIsEmpty(managedObjectContext: NSManagedObjectContext)-> Bool{
let defaultMusic = NSEntityDescription.entityForName("Music", inManagedObjectContext: managedObjectContext)
let defaultMusicSetOne = NSManagedObject(entity: defaultMusic!, insertIntoManagedObjectContext: managedObjectContext) as? Music
//defaultMusicSetOne = NSManagedObject(entity: defaultMusic!, insertIntoManagedObjectContext: managedObjectContext) as? Music
let fetchRequest = NSFetchRequest(entityName: "Music")
fetchRequest.entity = defaultMusic
defaultMusicSetOne!.uri = "spotify:track:1puJlKuYGH58SAFgXREUpE"
defaultMusicSetOne!.duration = 30.0
defaultMusicSetOne!.starttime = 49.0
defaultMusicSetOne!.xfade = 10.0
defaultMusicSetOne!.voiceover = "1.25s Next Up.mp3"
do{
if let MusicSetResults = try managedObjectContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]{
if MusicSetResults == 0
{
defaultMusic!.setValue(defaultMusicSetOne?.uri, forKey: "uri")
defaultMusic!.setValue(defaultMusicSetOne?.duration, forKey: "duration")
defaultMusic!.setValue(defaultMusicSetOne?.starttime, forKey: "starttime")
defaultMusic!.setValue(defaultMusicSetOne?.xfade, forKey: "xfade")
defaultMusic!.setValue(defaultMusicSetOne?.voiceover, forKey: "voiceover")
return true
}
else
{
return false
}
}
}
catch let error as NSError{
print("Error: \(error.debugDescription)")
}
return false
}
Music.Swift:
extension Music {
#NSManaged var voiceover: String?
#NSManaged var xfade: NSNumber?
#NSManaged var duration: NSNumber?
#NSManaged var starttime: NSNumber?
#NSManaged var uri: String?
}
But I keep getting the error "This class is not key value coding-compliant for the key uri" i'm sure this will be for all .setValues. I'm not sure why this is showing as defaultMusic does have the "uri" field and defaultMusic is also an instance of Music Object. Is the format of .setValue wrong? or is it something else?
Any help would be great!
Thanks in advance!!
I suspect you actually mean this:
Check if the entity is empty, if yes insert a new object with default values
class func musicIsEmpty(managedObjectContext: NSManagedObjectContext)-> Bool {
let musicEntity = NSEntityDescription.entityForName("Music", inManagedObjectContext: managedObjectContext)!
let fetchRequest = NSFetchRequest()
fetchRequest.entity = musicEntity
do {
let musicSetResults = try managedObjectContext.executeFetchRequest(fetchRequest) as! [Music]
if musicSetResults.isEmpty
{
let defaultMusicSetOne = NSManagedObject(entity: musicEntity, insertIntoManagedObjectContext: managedObjectContext) as! Music
defaultMusicSetOne.uri = "spotify:track:1puJlKuYGH58SAFgXREUpE"
defaultMusicSetOne.duration = 30.0
defaultMusicSetOne.starttime = 49.0
defaultMusicSetOne.xfade = 10.0
defaultMusicSetOne.voiceover = "1.25s Next Up.mp3"
return true
}
}
catch let error as NSError{
print("Error: \(error.debugDescription)")
}
return false
}
You might consider also to save the managed object context to make the new object persistent.
defaultMusic is a NSEntityDescription, not a Music object.
(Sometimes letting Swift use default data types can be confusing.)
I'm trying to save a bunch of data into core data. I could manage to save all the data into core data but it make no sense to have duplicate data inside core data because the function will be execute once on every build of the project. The approach that I am thinking now is empty the entity's record before I am adding new record to it. Just want to know is that got another way around to make sure that the data only persist once?
func persistCurrencyData() {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let currencyEntity = NSEntityDescription.entityForName("Currency", inManagedObjectContext: managedContext)
let countryEntity = NSEntityDescription.entityForName("Country", inManagedObjectContext: managedContext)
for currency in currencies {
let currencyObject = CurrencyCore(entity: currencyEntity!, insertIntoManagedObjectContext: managedContext)
currencyObject.setValue(currency.code, forKeyPath: "code")
currencyObject.setValue(currency.name, forKeyPath: "name")
for country in currency.country! {
let countryObject = CountryCore(entity: countryEntity!, insertIntoManagedObjectContext: managedContext)
countryObject.setValue(country.name, forKeyPath: "name")
countryObject.setValue(country.code, forKeyPath: "code")
countryObject.setValue(country.continent, forKeyPath: "continent")
countryObject.setValue(country.continentCode, forKeyPath: "continentCode")
countryObject.currency = currencyObject
}
}
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
}
Update:
Thanks to #Wain for giving me the idea. Basically what I done to solve the saving of duplicate data in core data is I included an GUID in my JSON file. Every time the code read the JSON file, it will save GUID into NSUserDefault and save data into core data. When it wants to read JSON file again in the next build, it will check whether the GUID is same as previous saved GUID. If it is different, the code will read the JSON file and repeat the process above. If not, it will proceed and ignore it.
func requestCurrenciesData() {
getCurrenciesDataFromFileWithSuccess { (data) -> Void in
var json: [String: AnyObject]
do {
json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as! [String: AnyObject]
guard let currencyData = CurrencyData(json: json) else {
print("Error initializing object")
return
}
self.currencies = currencyData.currency!
// Checking the JSON's id is same as previous read
let defaults = NSUserDefaults.standardUserDefaults()
if let id = defaults.stringForKey("id")
{
if id == currencyData.id {
let notification = NSNotification(name: "CurrenciesDataUpdate", object: nil)
NSNotificationCenter.defaultCenter().postNotification(notification)
return
}
}
self.persistCurrencyDataForId(currencyData.id)
let notification = NSNotification(name: "CurrenciesDataUpdate", object: nil)
NSNotificationCenter.defaultCenter().postNotification(notification)
} catch {
print(error)
return
}
}
}
func persistCurrencyDataForId(id: String) {
// Save the new JSON's id into NSUserDefaults
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(id, forKey: "id")
// Delete all the records in existing table
deleteRecordsFromEntity("Country")
deleteRecordsFromEntity("Currency")
// Insert the new records
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let currencyEntity = NSEntityDescription.entityForName("Currency", inManagedObjectContext: managedContext)
let countryEntity = NSEntityDescription.entityForName("Country", inManagedObjectContext: managedContext)
for currency in currencies {
let currencyObject = CurrencyCore(entity: currencyEntity!, insertIntoManagedObjectContext: managedContext)
currencyObject.setValue(currency.code, forKeyPath: "code")
currencyObject.setValue(currency.name, forKeyPath: "name")
for country in currency.country! {
let countryObject = CountryCore(entity: countryEntity!, insertIntoManagedObjectContext: managedContext)
countryObject.setValue(country.name, forKeyPath: "name")
countryObject.setValue(country.code, forKeyPath: "code")
countryObject.setValue(country.continent, forKeyPath: "continent")
countryObject.setValue(country.continentCode, forKeyPath: "continentCode")
countryObject.currency = currencyObject
}
}
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
}
func deleteRecordsFromEntity(entityName: String) {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let coordinator = appDelegate.persistentStoreCoordinator
let fetchRequest = NSFetchRequest(entityName: entityName)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try coordinator.executeRequest(deleteRequest, withContext: managedContext)
} catch let error as NSError {
print(error)
}
}
In general you can say 'if i have any currency entries in the data store, don't add more'. That could be done by adding a flag to use defaults or something like that when you do the first import, but a better option is to check the data store to see if it has any currency entries. To do this create a fetch request for currencyEntity and use countForFetchRequest:error: before you carry on with for currency in currencies {. If the count is greater than zero then you should return instead of processing the loop.
To be more efficient this check should actually be before you download and process the JSON...
I have a little problem with my NSManagedObject subclass and the core data.
So, I've created a subclass of NSManagedObject :
import Foundation
import CoreData
#objc(Alarm)
class Alarm: NSManagedObject {
#NSManaged var name: String
override init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?) {
super.init(entity: entity, insertIntoManagedObjectContext: context)
name = "Unnamed"
}
}
And I've created an AlarmsManager class to make the link with the Core Data
import UIKit
import CoreData
var alarmsMgr: AlarmsManager = AlarmsManager()
class AlarmsManager: NSObject {
let appDelegate: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let managedContext: NSManagedObjectContext
let entityDescription: NSEntityDescription
override init() {
managedContext = appDelegate.managedObjectContext!
entityDescription = NSEntityDescription.entityForName("Alarm", inManagedObjectContext: managedContext)!
}
func createAlarm() -> Alarm {
var newAlarm: Alarm = Alarm(entity: entityDescription, insertIntoManagedObjectContext: managedContext)
return newAlarm
}
func addAlarm(newAlarm: Alarm) -> Void {
var error: NSError?
println(newAlarm.name) // Display the good name
if !managedContext.save(&error) {
println("Error : \(error)")
}
println("Done...")
}
}
Also, this is how I load my Alarms :
func loadAlarms() {
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
let fetchRequest = NSFetchRequest(entityName:"Alarm")
var error: NSError?
let fetchedResults = managedContext.executeFetchRequest(fetchRequest, error: &error) as [NSManagedObject]?
if let results = fetchedResults {
alarmsArr = results
}
else {
println("Could not fetch \(error), \(error!.userInfo)")
}
}
So :
1) I create a new Alarm using createAlarm
2) I fill the name of my Alarm using something like newAlarm.name = "New name"
3) I try to save this object in my Core data using addAlarm from the AlarmManager
The problem is : It does save the object in the core data but when I try to display my alarms, the names are still "Unnamed"
Thank you in advance for your help
I added the NSManagedObjectContext rollback function in the function that display the AlarmsListView and my problem was solved.