Swift Core Data Relationships and Seeding Data - ios

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.

Related

How do I convert my Card to an NSSet?

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.

Correct way to use CoreData Entity in this example

I have a CoreDataentity called "Person" with 3 attributes
import Foundation
import CoreData
class Person: NSManagedObject {
#NSManaged var name: String
#NSManaged var image: NSData
#NSManaged var info: String
}
EDITED:
EXAMPLE: (I think my first "Table/chair" example caused more confusion then explanation.)
I have 7 UIButtons. When a UIButton is tapped, My TableView is modally pushed and TableView cells are populated with attribute values of all available Person via
var request = NSFetchRequest(entityName: "Person")
request.returnsObjectsAsFaults = false;
results = context.executeFetchRequest(request, error: nil)!
I want to be able to double-tap a cell, which will close TableView and return to mainViewController the Person selected, and attach it to the UIButton that called the TableView. This UIButton will now display SelectedPerson's image, name, and info (or have direct access to attribute values).
How should I go about making this possible?
Is there a NSFetchRequest Method that I could simply input something like...
NSFetchRequest(entityName: "Person", attributeName: "name", String == "John Doe")
If so I could just pass the "name" value from TableView to MainViewController, and store a Person reference in MainViewController via something like this...
var PassedNameString = "John Doe"
var Person1: Person = NSFetchRequest(entityName: "Person", attributeName: "name", String == PassedNameString) as! Person
This way I'll have a reference to what Person entity is filling a "seat" and have direct access to it's info to read or edit.
My CoreData understanding is very low.
I've only learned how to create/store a file. I do not have a understanding of what is needed to recall a CoreData file and Edit if needed. I also don't know what kind of demand is put on the device when Fetching Vs holding a Reference to CoreData entry.
In your view controller have an array (or a dictionary if it suits you better) for the chairs. Each slot in the array represents a chair, and the index of the array links to the buttons you have (also should be in an array). The chair array holds instances of Person and is initially empty.
The table view controller should use a fetched results controller, with a batch size on the fetch. This is used to populate the table as usual. When a table row is selected the associated Person should be passed back and stored in the chair array.
Doing a second fetch is pointless and leads to name duplication issues.
Think of CoreData as your database, that stores the data that is categorized by your model structures.
From your description, I would do a fetch request somewhere in your ViewController's loading sequence, say in ViewDidLoad, and store the results (Person objects) in an array of persons ... var persons = [Person](). This can be a class variable. Then I would access the Person objects by calling array[index].
Let me know if this helps you with your thinking, or where you are confused.
var persons = [Person]()
override func viewDidLoad() {
...do your fetch call...get back an array of objects as a result
for person:Person in result {
self.persons.append(person)
}
// now to access your Person objects anywhere in the class just call
// self.persons[index] to get back an Person object
}

Core Data passing objectID to another thread

I've tried Googling and searching SO, thinking it would be quite easy to find but I'm surprised there were little to no examples of my problem in Swift.
I have several threads and from what I've read, it's best to have a separate managed object context for each thread. If I want to access an object from another context, I should pass around the objectID.
My question is, how should I pass the objectID to the context in a new thread?
Below is how the construction of my situation is:
func doSomething(context: NSManagedObjectContext) {
let person = Persons(context: context) // NSManagedObject
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) {
let background = (UIApplication.sharedApplication().delegate as! AppDelegate).cdh.backgroundContext!
Birthdays(context: background, person) // NSManagedObject
saveContext(background)
}
}
Birthdays has a one to one relationship with Persons. If I execute this code it will give me an error saying:
Illegal attempt to establish a relationship 'person1' between objects in different contexts
Obviously because they are in separated contexts. So I tried to get the objectID of person1 by let personsObjectID = person1.objectID, but I don't know how I should be using it to pass it to the other thread. Any help is appreciated.
Seems like the only thing I needed to do was to add:
let person = background.objectWithID(orders.objectID as! Persons
If I've understood it correctly, objectWithID fetches the object from the store and puts it in the context, returning the managed object. It would be nice if someone could verify this if it's true.
func doSomething(context: NSManagedObjectContext) {
let person = Persons(context: context) // NSManagedObject
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) {
let background = (UIApplication.sharedApplication().delegate as! AppDelegate).cdh.backgroundContext!
let person = background.objectWithID(orders.objectID as! Persons
Birthdays(context: background, person) // NSManagedObject
saveContext(background)
}
}
Hendrik, you are correct, you should use objectWithID to instantiate the NSManagedObject on the required context.

Core Data relationships (swift)

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)

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