How do I convert my Card to an NSSet? - ios

I am trying to add a new Card to a Deck of cards, but am running into an issue when trying to save my card to the deck's cards. How do I convert it to an NSSet?
func saveQA(question: String, answer: String) {
let currentDeckName = deckName
let entity = NSEntityDescription.entityForName("Card", inManagedObjectContext: managedContext)
let newQA = Card(entity: entity!, insertIntoManagedObjectContext: managedContext)
newQA.question = question
newQA.answer = answer
currentDeckName!.cards = newQA
do {
try managedContext.save()
userCards.append(newQA)
}
catch {
let saveError = error as NSError
print(saveError)
}
}

Here, you're trying to assign a Card to a property that is expecting an NSSet. A card is not a set (although it could be an object in a set).
currentDeckName!.cards = newQA
Taking advantage of type safety
Since you are coding in Swift, the first thing you should do is use Swift types such as Set instead of NSSet. In your Deck+CoreDataProperties.swift, change your relationship from NSSet? to a typed set of cards.
#NSManaged var cards: Set<Card>
This not only tells Swift that cards is a Set, but also specifies that the objects in the set will be Cards.
You always want to declare specific types for relationships to benefit from the language's type safety. This allows the compiler to prevent you from ever adding anything other than a Card to the deck's set of cards.
Adding a card to the deck
You could use the usual Set methods such as insert to insert a card into a deck's set.
currentDeckName.cards.insert(newQA)
However, a much easier way is to use the reverse relationship on the card itself. In other words, tell the new card that it belongs to this deck:
newQA.deck = currentDeckName
That will automatically add the card to the deck's set of cards.
It's a bit less code, and less is generally better to read and understand.
Speaking of readability, you may want to consider renaming currentDeckName to currentDeck since that object is a Deck, not the name of a deck.

For converting your object to MutableSet please verify with following step.
1) Select your model object
2) select your parent entity
3) Select Relationship which is one To many
4) now check following image property is one To many or not.

Related

Using CoreData to save array containing dictionaries

I'm trying to use CoreData framework inside an iOS app for the first time and I have some troubles with it.
I have a kind of complex type of array, containing tuples that contains dictionaries:
let DataSet: [( Float, Float, [String : String])] = ...
I created an NSManagedObjectContext and an NSManagedObject, I added the DataSet array to the NSManagedObject, but when I try to save the 1st NSManagedObjectContext, the app is crashing.
That's not happening using normal arrays but only with more complex ones like the one above.
Is there a way to deal with that problem? Do I need to adjust my xcdatamodel?
I would appreciate any help a lot.
First you need to declare 2 separate entities. One for Floats and another for Dictionary.
Then create "One to Many" relation ship between Float entity and Dictionary. It will look like this,
Then you need to convert your tuple into these 2 entities, Like this,
dataSet.forEach{ (member) in
let tuple = Test.(context: mainContext)
tuple.first = member.0
tuple.second = member.1
let dictionary = member.2
dictionary.forEach{ (key, value) in
let dick = Dick.(context: mainContext)
dick.key = key
dick.value = value
tuple.addToContains(dick)
}
}
Finally you need to save the values.
if mainContext.hasChanges {
do {
try mainContext.save()
} catch {
print(\(error))
}
}

How to create initial Realm objects that get added upon installation of app

Say I am creating an object that takes two strings and acts like a dictionary.
class WordInDictionary: Object {
#objc dynamic var word: String = ""
#objc dynamic var meaning: String = ""
What should I do if I wanted to have some initial objects that get added to the database just once upon installation/update of the app?
Also, is there a way to make it so that just those initial objects can't be deleted?
"What should I do if I wanted to have some initial objects that get added to the database just once upon installation/update of the app?"
One option would be to have some code near the realm initialisation that checks if there are any WordInDictionary objects already in the realm - if not then add the required default objects.
E.g.
let realm = try! Realm()
if realm.objects(WordInDictionary.self).isEmpty
{
// Add required words here
}
"Also, is there a way to make it so that just those initial objects can't be deleted?"
I don't know of a way to make realm objects read-only. You'd have to implement this in code in some way, e.g. have a isDeletable boolean member which is true for every user-created object and false for your default members, then only delete those from realm.
E.g. for your deletion code:
func deleteWords(wordsToDelete: Results<WordInDictionary>)
{
try! realm.write
{
realm.delete(wordsToDelete.filter("isDeletable = true")
}
}

Core Data many to many relation with intermediate table (Swift 2)

TL;DR EDIT with answer
As Wain perfectly answered this is how I get my information now:
let ingredientsToRecipe = recipe.valueForKey("ingredientsToRecipe")! as! NSSet
for i in ingredientsToRecipe {
print(i.valueForKey("amount")!)
print(i.valueForKeyPath("ingredient.name")!)
}
Original question
I have a huge problem understanding the usage of intermediate tables in CoreData. I've searched SO for answers and found a few threads about intermediate tables and many-to-many relations but those where either Objective-C or didn't help me.
I have the following setup (simplified):
Now I want to add a new recipe with a bunch of ingredients.
Let's say a Burger. The burger consists of
1 cocumber,
1 Tomato,
1 Meat,
2 bread
(yummy...)
This is what I tried so far:
// Core Data
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let entity = NSEntityDescription.entityForName("Recipe",
inManagedObjectContext:managedContext)
// creating a new recipe with name and id
let recipe = NSManagedObject(entity: entity!,
insertIntoManagedObjectContext: managedContext)
recipe.setValue("Burger", forKey: "name")
recipe.setValue("B_001", forKey: "id")
Now I got an Array:[NSManagedObject] of of ingredients (created just like the Burger) and a Dictionary of amounts to the ingredient_IDs. This is how I'm trying to marry my Recipe with the ingredients (over the intermediate table).
for i in selectedIngredients { // the ingredient array
let ingredientsToRecipe = NSEntityDescription.insertNewObjectForEntityForName("RecipeIngredient", inManagedObjectContext: managedContext)
ingredientsToRecipe.setValue(i, forKey: "ingredient")
ingredientsToRecipe.setValue(recipe, forKey: "recipe")
let quantity = Double(quantityDictionary[(i.valueForKey("id") as! String)]!) // the amount-to-ID dictionary
ingredientsToRecipe.setValue("\(quantity)", forKey: "quantity")
}
In the end I simply save everything:
do {
try managedContext.save()
print("Saved successfully")
self.dismissViewControllerAnimated(true, completion: nil)
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
All this above somehow works. But now I'm struggling to fetch information about my recipes.
How am I supposed to fetch the amount of tomatoes of this specific burger?
Things like
recipe.valueForKey("RecipeIngredient").valueForKey("amount") work but I don't know which amount is from which ingredient.
Am I doing anything wrong?
What can/should I do better?
The goal is to create a recipe with ingredients and later populate a Table with information about the recipe and the amounts of it's ingredients (and the ingredients themselves).
I appreciate any help!
The power of the intermediate object is that it takes your many-to-many relationship and breaks it into multiple one-to-many relationships. The to-one relationships are easy to navigate.
So, from your Recipe you can get an array of RecipeIngredients, and for each one you can get valueForKey("amount") and valueForKeyPath("ingredient.name").
For you to get the amount of an ingredient for a specific recipe you can create a fetch request at RecipeIngredient using predicates like this :
var request = NSFetchRequest(entityName: "RecipeIngredient")
let predicate = NSPredicate(format: "recipe.name = %# AND ingredient.name = %#", "burger","tomato")
request.predicate = predicate
Then you simply get tha amount value from the returned RecipeIngredient entity.
You don't need to use valueForKey and valueForKeyPath to access these kinds of properties... Rather, you should let Core Data do the work of traversing the relationships for you, and just ask for what you need using dot syntax:
for item in recipe.ingredientsToRecipe {
print(item.amount)
print(item.ingredient.name)
}
I suggest that you rename your intermediate entity from IngredientsToRecipe (which is thinking like database design) to Items or ReceipeItems to better capture what it is—the item that actually appears in a recipe, rather than the underlying food type itself.
But whether you do that or not, you could certainly name the relationship on Receipe to be items, resulting in the much more readable:
for item in recipe.items {
print(item.amount)
print(item.ingredient.name)
}
You could also go further and create a computed property on the intermediate entity called name that simply returned ingredient.name, which would then let you use:
for item in recipe.items {
print(item.amount)
print(item.name)
}
:)

Swift Core Data Relationships and Seeding Data

I am currently parsing JSON data and either updating or creating an entity based on whether a results exists.
I am using SwiftyJson for my JSON parsing.
I have a createInManagedObjectContext function inside my NSManagedObject Subclass that accepts a bunch of parameters for creating a new record:
class func createInManagedObjectContext(moc: NSManagedObjectContext, id: String, flatNumber: String, propertyName: String, propertyNumber: String, street: String, locality: String, town: String, postcode:String, createdDate: NSString) -> Work {
let newWorkItem = NSEntityDescription.insertNewObjectForEntityForName("Work", inManagedObjectContext: moc) as! Work
var mydate = createdDate
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
newWorkItem.createdDate = formatter.dateFromString(mydate as String)!
newWorkItem.id = id
newWorkItem.flatNumber = flatNumber
newWorkItem.propertyName = propertyName
newWorkItem.propertyNumber = propertyNumber
newWorkItem.street = street
newWorkItem.locality = locality
newWorkItem.town = town
newWorkItem.postcode = postcode
return newWorkItem
}
and here is the code I am currently using to parse the json and create a new record:
if let moc = self.managedObjectContext {
moc.performBlockAndWait({
Work.createInManagedObjectContext(moc,
id: object["Id"].stringValue,
flatNumber: object["FlatNumber"].stringValue,
propertyName: object["PropertyName"].stringValue,
propertyNumber: object["PropertyNumber"].stringValue,
street: object["Street"].stringValue,
locality: object["Locality"].stringValue,
town: object["Town"].stringValue,
postcode: object["Postcode"].stringValue,
createdDate: object["CreatedDate"].stringValue
)
for party in object["Parties"].arrayValue {
Party.createInManagedObjectContext(moc,
id: party["Id"].stringValue,
firstName: party["FirstName"].stringValue,
lastName: party["LastName"].stringValue,
propertyName: party["PropertyName"].stringValue,
propertyNumber: party["PropertyNumber"].stringValue,
street: party["Street"].stringValue,
locality: party["Locality"].stringValue,
town: party["Town"].stringValue,
postcode: party["Postcode"].stringValue,
createdDate: party["CreatedDate"].stringValue)
}
// println(object["Parties"])
})
moc.save(nil)
}
Now I know this isn't the best way of doing this kind of operation, and in honesty this schema is going to be quite big, there will be a lot of records in other entities that will rely on this Work entity.
I thought I would begin with parties as there can be many parties, but I am unsure as to how to link each party with the Work entity. I did experiment with passing in the workId and thought perhaps I need to pass in a Work object back into the Work managed object but I am positive there are far better approaches than having sprawling functions such as these?
So my question is, what would be the best approach in this situation to handle creating entities with multiple relationships?
Update:
I have changed my implementation as follows:
let work = NSEntityDescription.insertNewObjectForEntityForName("Work", inManagedObjectContext: moc) as! Work
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
work.id = object["Id"].stringValue
work.flatNumber = object["FlatNumber"].stringValue
work.propertyName = object["PropertyName"].stringValue
work.propertyNumber = object["PropertyNumber"].stringValue
work.street = object["Street"].stringValue
work.locality = object["Locality"].stringValue
work.town = object["Town"].stringValue
work.postcode = object["Postcode"].stringValue
work.createdDate = formatter.dateFromString(object["CreatedDate"].stringValue)!
for obj in object["Parties"].arrayValue {
let party = NSEntityDescription.insertNewObjectForEntityForName("Party", inManagedObjectContext: moc) as! Party
party.id = obj["Id"].stringValue
party.firstName = obj["FirstName"].stringValue
party.lastName = obj["LastName"].stringValue
party.propertyName = obj["PropertyName"].stringValue
party.propertyNumber = obj["PropertyNumber"].stringValue
party.street = obj["Street"].stringValue
party.locality = obj["Locality"].stringValue
party.town = obj["Town"].stringValue
party.postcode = obj["Postcode"].stringValue
party.createdDate = formatter.dateFromString(obj["CreatedDate"].stringValue)!
//doesn't work
work.parties.addlistObject(party)
}
I did try implementing the NSSet solution described below but ran into problems where the current for loop that is iterating over my JSON is running
Edit:
I have managed to get it working by adding
party.work = work as Work
Inside the for loop as suggested below.
Now it runs fine for a while and seems to be doing the right thing until it falls over with an error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Is this a separate or related issue?
What you are trying to do is actually pretty simple with Core Data. What you are asking for is a way to connect two entities together with one another.
Now, it seems as if you want multiple Parties under a Work entity. This means you are looking at a To-Many relation between the Work entity and the Parties entity. All you need to do is create a relationship between the two entities that will look like this:
First, go you your Work entity under the "Relationships" tab and click the "+" button. Name this new relationship "parties" and then click enter. Make the destination your Parties entity.
Second, go to your Parties entity and do the same, naming the relationship "work" and setting its destination to your Work entity. But this time, click the drop-down menu under "Inverse" and select your parties relationship to form a To-One relation beween your Work and Parties entities. Now, each instance of your Work entity holds a variable that holds an instance of the opposite Parties entity and vice versa.
However, you probably want multiple instances of your "Parties" entity connected you one Work instance, and so now you want to change the relationship to a To-Many relation. We can do this by going to your Work entity and clicking on the "parties" relation. Now, in the Data Model Inspector to the right, look for the menu that says "Type". Click on the menu and choose "To Many" from the drop-down.
You now have a To-Many relationship between entities!
Now, to add the connection in code, open your Parties.swift file and, at the bottom, type:
#NSManaged var work: Work
This gives a reference to the work object that the party is connected to. You can set this while creating your Parties instance in for party in object["Parties"].arrayValue ... }. Just use
party.work = {Your Work instance}
For the Work class, however, things are slightly different. You see, the way we have set up our relationship, the Parties entity can only be under one work entity but the Work entity can contain many Parties. Core Data will store these as an NSSet (or NSOrderedSet if you want to order the Parties objects in the set). So add this code to the bottom of Work.swift:
#NSManaged var parties: NSSet
This creates an NSSet instance that will contain all Parties under the Work instance. Now you can add a party by creating a new NSSet instance and assigning it to the parties variable like so:
let newSet = NSMutableSet(setWithSet: parties)
newSet.addObject({party variable})
myWork.parties = newSet.copy() as! NSSet
You can then, if you have a workID, check the ID by using myParty.work.workID == "SomeWorkID". You can also enumerate through the parties NSSet if you need to.
Hopefully this answer helps you with what you're trying to accomplish! Let me know if this doesn't answer your question fully.
If you want to do more researching about CoreData relationships, you can get a book on Core Data (I bought "Core Data by Tutorials" by Ray Wenderlich) or look over Apple's documentation here.

Swift CoreData: Inserting instances of an Entity related to other 2 entities

I have an app with the following entities: Station, Program, and StationProgram :
Station <-->> StationProgram <<--> Program
Station refers to an electrovalve, Program is a watering program (there can be N programs in the database), and StationProgram has an attribute that indicates the time that a Station will water in a Program.
The point is that I can create correctly instances of Station and Program in the DB (in their respective View Controllers). However, I have a TableViewController where, for a given station selected in a previous controller, I want to show all the available programs, with a UISwitch indicating if this program has been associated to the station or not. Initially, there are no associations between stations and programs. The user can interact with all the existing programs in the DB and active them for this station (setting the UISwitch shown in the table row that points to the program to on). Finally, when the user wants to save the configuration, I want to insert the data in the table StationProgram. By now, to simplify, I just want to assign a time manually, for example, 2 minutes, to the programs active. I have the following code, but the execution crashes when I try to map this:
#IBAction func saveTapped(sender: AnyObject) {
// Reference to our app delegate
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
// Reference moc
let context: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("Station", inManagedObjectContext: context)
station.setValue(textFieldStationName.text as String, forKey: "name")
let en2 = NSEntityDescription.entityForName("StationProgram", inManagedObjectContext: context)
// The variable activePrograms contains the row of the table where this program is shown, and the wateringTime
for (selectedCellId,time) in activePrograms {
var newStationProgramInstance = StationProgram(entity: en2, insertIntoManagedObjectContext: context)
let program: NSManagedObject = programList[selectedCellId] as NSManagedObject
// Map our properties
newStationProgramInstance.wateringTime = time
// station is a variable of type Station that is filled from the previous controller
newStationProgramInstance.toStation = station as Station
newStationProgramInstance.toProgram = program as Program
}
context.save(nil)
self.navigationController.popViewControllerAnimated(true)
}
Specifically, the execution crashes at the line "newStationProgramInstance.toStation = station as Station". It says swift_dynamicCastClassUnconditional at
Thread 1: EXC_BREADKPOINT (code=EXC_I386_BPT, subcode=0x0)
Thank you very much for your help.
In Core Data you would model this kind of relationship with a many-to-many relationship, not a separate join table.
Station (programs) <<----->> (stations) Program
The only justifiable reason to use a join table is if you want to add and keep additional information about the relationship itself (such as dateCreated or similar). I doubt that is so in your case.
The creation of the relationship now becomes trivial. It is enough to just do it one way if the model is set up correctly with reverse relationships.
newStation.addProgramsObject(newProgram)

Resources