Core Data relationships (swift) - ios

I'm building an app that requires a core data relationship as such:
entityA <<---> entityB (e.g. any given entityA can hold many entityB objects)
I have two tableviews with entityA list items in which I want to be able to store entityB objects in any given entityA object.
I'm new to using relationships with core data (and fairly new to swift) and would like to learn how to make this work. Does anyone have any swift tutorials in mind that would be good for me to take a look at or any other resources that may help me learn?
Sorry if my question doesn't make much sense, ask me to clarify if you need.
Thanks!
UPDATE:
Here's a bit more specificity on what I'm wanting to learn.
Lets say I have the entity "Person" (attributes may include name, age, etc.) and a tableview in which my app users can add a person to. (this I have established and know how to do appropriately) But, now I want to add the entity "Meal" (attributes may include food items), and Meal is a tableview of its own that I can access by choosing the person that I want to add a meal to. Each person can have more than one meal, but there can only be one person per meal.
The question is: what would my core data model, fetchRequests, etc look like in order to accomplish this?
Hope that is clear enough! :)
Thanks
Here's a code snippet of my function for creating a meal:
func createMeal() {
let entityDescription = NSEntityDescription.entityForName("Meal", inManagedObjectContext: managedObjectContext!)
let meal = Meal(entity: entityDescription!, insertIntoManagedObjectContext: managedObjectContext)
meal.mealName = mealNameTxt.text
meal.mealItem1 = mealItem1Txt.text
managedObjectContext?.save(nil)
}

Well, it's pretty simple. Let's have an example, you have a branch and the branch has lots of specifications. Firstly you need to go to your xcdatamodel and create your data entities
Then you open you editor (table style) and make the relation in your branch entity
After that you will need to set up the relation typo in your branchSpecs too
And that's it! You have just created a relationship between your CoreData entities. All you need to do is to generated the subclassed objects
And now you're all set. You will find a NSSet * object in your branch class that holds the data related specs of that branch. Also your will find a method called addSpecsObject that you can use to store the specs object.
A code sample in my case:
Branch * branch = [NSEntityDescription insertNewObjectForEntityForName:#"Branch"
inManagedObjectContext:managedObjectContext];
branch.name = obj.name;
branch.lattitude = obj.latitude;
branch.longitude = obj.longitude;
branch.dispalyedDescription = obj.dispalyedDescription;
for (FLBranchesSpecs * spec in obj.branchSpecs) {
BranchSpecs * branchSpec = [NSEntityDescription insertNewObjectForEntityForName:#"BranchSpecs"
inManagedObjectContext:managedObjectContext];
branchSpec.type = #(spec.type);
branchSpec.key = spec.key;
branchSpec.value = spec.value;
[branch addSpecsObject:branchSpec];
}
NSError *error;
if (![managedObjectContext save:&error])
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
A something similar than what you want
let person: AnyObject = NSEntityDescription.insertNewObjectForEntityForName("Person", inManagedObjectContext: self.managedObjectContext!)
//do you code assignment here
for meal in listOfMeals{
person.addMealObject(meal)
}
var error: NSError?
self.managedObjectContext?.save(&error)

Related

Why do we need to call context.delete to delete an item from NSManagedObject array?

Suppose I have;
var itemArray = [Item]()
and Item is a NSManagedObject. Item has two attributes "Title":String and "Done":Boolean. Here is the picture of my data model.
// Item+CoreDataClass.swift
// This file was automatically generated and should not be edited.
//
import Foundation
import CoreData
public class Item: NSManagedObject {
}
When I change the value of Done and call context.save, it is automatically reflected to Persistent Container. However, when I remove an element from array by saying,
itemArray.remove(at: someindex)
and call context.save. The item is not deleted from Persistent Container. Only if I called,
context.delete(itemArray[someindex])
then the item is truly deleted from store.
So why only removing from itemArray and save context is not sufficient although changing an attribute' value and save context is sufficient for successful CRUD operation on Core Data?
When you change an attribute on an Item object then Core Data (actually the NSManagedObjectContext) detects that since the Item belongs to the NSManagedObjectContext and the item is marked as dirty. But your array has no connection to the NSManagedObjectContext in any way so any changes you make it to remains undetected by the NSManagedObjectContext and that is why you need to tell it explicitly that you want to delete the item you removed from the array.
Another way to look at it is that anything you create/define in your Core Data model is known by NSManagedObjectContext but anything created outside in swift code is unknown. If you start working with to-many relationships between entities you will see that then adding or removing objects from the to-many collection will be handled directly by the NSManagedObjectContext in a way you expected for your array.
The array var itemArray = [Item]() has no direct relation with the underlying database. Therefore removing items from that array doesn't affect the Core Data database.
To create, save or delete NSManagedObject instances in a Core Data database you need to call the related functions of a valid NSManagedObjectContext.
Any operation on CoreData should be done through NSManagedObjectContext as it is the scratchpad to access or update any entity in database. So in your case while deleting the Item entity, you should do that through context only to get reflected on database.
var itemArray = [Item]()
let context = //get your context
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Item")
let predicate = NSPredicate(format: " (primaryKey == %#) ", "your_primary_key")
fetchRequest.predicate = predicate
itemArray = try! context.fetch(fetchRequest)
for i in 0 ..< itemArray.count where i < itemArray.count-1 {
context.delete(itemArray[i])
}
do {
try context.save()
} catch {
// print the error
}

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 - Accessing multiple entities in core data at the same time

I'm saving objects to my core data, and now I need to save data to two different entities at once.
Following my own logic, if I normally save my data like this (which works perfectly):
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let contxt: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("MainTable", inManagedObjectContext: contxt)
var newMessage = SecondModel(entity: en!, insertIntoManagedObjectContext: contxt)
newMessage.receiver = object["Receiver"] as String
contxt.save(nil)
I should be able to save to another entity by creating another variable with my different entity like this:
let enet = NSEntityDescription.entityForName("AlreadyRec", inManagedObjectContext: contxt)
var secondMessage = ThirdModel(entity: enet!, insertIntoManagedObjectContext: contxt)
This gives me the error saying the "ThirdModel has no available initializers, and I don't know what this means or how to solve this issue. Please help me solve this problem.
Any suggestions on how to proceed would be highly appreciated.
Your context covers all of Core Data. Just save at the end.
create / edit entity1
create / edit entity2
save
Some advice: get away from your nondescript naming conventions. Don't call your entity MainTable - that does not even make sense in the SQL world! To my horror I noticed that your managed object model subclass is not called MainTable but SecondModel.
If your data describes widgets, you should call your entity and its subclass Widget. If it is something abstract, such as programming habits, you could call it Habit. Get the point?

iOS multiple entites for one managedObjectContext

There is a core data model with two entities i my iOS application.
Entity: Unit / Attributes: unit_name - NSString
->> Relationship to Exercise (one to many)
Entity: Exercise / Attributes: exercise_name - NSString .
So one unit can have many exercises.
In my table view controller are all available exercises listed.
(So in the first time, i make a fetch request for the Exercise entity and the managedObjectContext points to this entity.
If i want to save a "NEW" unit with exercises the save function doesn't work.
There is no error at all, but the unit table is still empty.
Here is the code for the save function:
Units *newUnit = [NSEntityDescription insertNewObjectForEntityForName:#"Units" inManagedObjectContext:[self.coreDataHelper managedObjectContext]];
newUnit.unit_name = unitTextField.text;//Attribute
newUnit.exercises = exerciseSet;//Relationship (NSSet)
NSError *error = nil;
if (![[self.coreDataHelper managedObjectContext]save:&error]) {
NSLog(#"There was an error! %#", error);
}
else {
NSLog(#"Success!");
}
It seems like the managedObjectContext still points to the Exercise entity. (Because it was initialized the first time with this entity) the coreDataHelper has the NSPersistentStoreCoordinator, the NSManagedObjectContext, the NSManagedObjectModel and some methods to read write and delete.
Thanks for help!
Just to verify that everything is connected the way it ought to be, add
NSAssert(self.coreDataHelper, #"null coreDataHelper");
NSAssert(self.coreDataHelper.managedObjectContext, #"null MOC");
NSLog(#"available entities: %#",
self.coreDataHelper.managedObjectContext.persistentStoreCoordinator.managedObjectModel.entitiesByName);
You ought to see "Units" as one of your entities, if you've set everything up correctly.
And then after your insertion of newUnit, verify that it worked:
NSAssert(newUnit, #"newUnit didn't get inserted");
This smells like a logic error to me, by the way: you're creating a new Units instance every time you save? Are you sure you don't want to use a find-or-create pattern instead?

Resources