Core Data simple relationship - NSSet - ios

I am trying to play with relationships in xCode and I'm having some problems:
I want to create a simple User to favoriteThing relationship.
I created a model, so every user looks like this:
extension User {
#NSManaged var name: String?
#NSManaged var favoriteThings: NSSet?
}
extension FavoriteThing {
#NSManaged var thingName: String?
#NSManaged var user: User?
}
And I got a tableview conected (so it displays favoriteThing for each user).
The thing is, when I create a new favoriteThing how to add this thing to favoriteThings NSSet that is created in User class?
And what about when I delete this favoriteThing from my table view? (it is managed by fetchedResultsController) How to delete it also from mentioned favoriteThings NSSet?
Any help appreciated!

In the case of a one-to-many relationship, the easiest way to
add an object to the to-many relationship is to set the property
of the inverse to-one relationship:
theFavoriteThing.user = theUser
This automatically updates theUser.favoriteThings to include
theFavoriteThing.
And to remove it just set the inverse to-one relationship to nil:
theFavoriteThing.user = nil

Related

App is terminating when trying to get an object value

My homework is to get a JSON array and save it to CoreData and then fetch it to a UITableView.
This is my first time working with CoreData and have already saved objects in CoreData, but when fetching I couldn't get an Object from the class I defined but instead I got [NSManagedObject].
So I added #NSManaged on every field in my defined class, but then the app terminates.
Movie.swift
import Foundation
import CoreData
class Movie :NSObject, Codable{
#NSManaged var title:String
#NSManaged var image:String
#NSManaged var rating:Double
#NSManaged var releaseYear:Int
#NSManaged var genre:[String]
init(title:String, image:String, rating:Double, releaseYear:Int, genres:[String]) {
super.init()
self.title = title
self.image = image
self.rating = rating
self.releaseYear = releaseYear
self.genre = genres
}
var title1:String{
get{
return title
}
}
}
The main problem is supposed to be in Movie.swift, I looked it up online and I couldn't find much that could help.
Could it be that I need to add NSManagedObject instead of NSObject?
Some issues
A class representing a Core Data entity must be a subclass of NSManagedObject. Let Xcode create the subclass(es) on your behalf.
A custom init method cannot be used unless it's a convenience initializer calling init(entity:insertInto:) or other designated initializers.
It's not trivial to implement Codable. Please see How to use swift 4 Codable in Core Data
For further information please read the Core Data Programming Guide
If you have a core data model, Xcode can generate the model class for you. Open the xcdatamodel file and in the menu bar, go to Editor -> Create NSManagedObject Subclass.

Core Data One-To-Many Swift

The question i have is in regards to a Core Data one-to-many relationship as of right now i have my app being able to let the user input employee information and saving to core data, then updating the employee table view. The problem i face is the relationship between the employee and delivery. Im currently trying to display a specific employees deliveries when clicking on an employee in the employee table view. After selecting an employee from the employee tableView i want it to segue to another tableview and display the employees deliveries in another UITableview.
What I'm trying to Accomplish:
1) Display Specific Employee's Deliveries
2) Add deliveries to the NSSet
Here are my two managedObjects
extension Delievery {
#NSManaged var address: String?
#NSManaged var phoneNumber: String?
#NSManaged var subTotal: NSNumber?
#NSManaged var total: NSNumber?
#NSManaged var employee: Employee?
}
extension Employee {
#NSManaged var first: String?
#NSManaged var last: String?
#NSManaged var phoneNumber: String?
#NSManaged var wage: NSNumber?
#NSManaged var id: NSNumber?
#NSManaged var delievery: NSSet?
}
how i prepared for segue from employeeTableViewController to deliveryTableViewContorller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue == "DelieverySegue"{
let employeeDeliveries = segue.destinationViewController as! DelieveryTableViewController
let indexPath = self.tableView.indexPathForSelectedRow
let selectedEmployee = employees[indexPath!.row]
employeeDeliveries.employee = selectedEmployee
}
}
The variables of the deliveryTableViewController are
var employee: NSManagedObject!
var deliveries : [NSManagedObject]!
var managedObjectContext : NSManagedObjectContext!
In this photo it shows the rest of my deliveryTableViewController the problem i have is how do i return the amount of deliveries for a specific employee in the numberOfRowsInSection function and how do i fetch the deliveries of the employee.
deliveryTableViewController set up
In this last photo my question is how to i add this delivery entry to the employee selected? this is how I'm currently saving my delivery entries
how i save my delivery entries
If you made it this far i appreciate you looking through my problem. If anyone can help me the slightest with this issue i'd appreciate it if you feel i've left some information out that is needed please let me know.
UPDATE:
Here is the picture of the DelieveryTableViewController (yes i know i spelt delivery wrong)
also need to set the NSPredicate
this is home I'm preparing for segue in EmployeeTableViewController
these are my variables for EmployeeTableViewController
Setting the relationship
With one to many relationships, it is easiest to set the to-one relationship:
delivery.employee = employee
Put this line in your completeOrder method (you may need to pass the employee reference from a previous VC). CoreData will automatically set the inverse relationship for you, adding delivery to the deliveries set for the employee.
Showing the Deliveries in a Table View
Having set the relationship, the deliveries property of employee contains a set of Delivery objects to which it is related. To display these in a table view, create your deliveries array from this set (eg. in viewDidLoad):
deliveries = employee.delivery.allObjects()
Your numberOfRowsInSection can then just use deliveries.count and the cellForRowAtIndexPath can use deliveries[indexPath.row] to determine which Delivery to display in each cell.
(An alternative is to fetch the deliveries array in the normal way, but to use a predicate to restrict the results to those that are related to your employee:
fetch.predicate = NSPredicate(format:"employee == %#", employee)
Longer term, you should consider using NSFetchedResultsController which is designed for displaying CoreData objects in table view.)
Update
You don't need the thisEmployee variable. Just change the Employee variable to be Employee class:
var employee : Employee!
Then you should be able to set
deliveries = employee.deliveries?.allObjects as! [NSManagedObject]
And in your fetchDelivery() method, set the predicate with
fetchRequest.predicate = NSPredicate(format:"employee == %#", employee)
(after let fetchRequest = ....).
Update 2
It's difficult to see where the nil value is. To track it down, try printing the value of employee in the viewDidLoad method of the DelieveryTableViewController. If it's nil, there is a problem with passing the value in prepareForSegue. If not, print employee.deliveries, etc. Post your results.

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.

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
}

Setting an NSManagedObject relationship in Swift

How does one add an object to a relationship property in an NSManagedObject subclass in Swift?
In Objective-C, when you generate an NSManagedObject subclass in Xcode from the data model, there's an automatically generated class extension which contains declarations like:
#interface MyManagedObject (CoreDataGeneratedAccessors)
- (void)addMySubObject: (MyRelationshipObject *)value;
- (void)addMySubObjects: (NSSet *)values;
#end
However Xcode currently lacks this class generation capability for Swift classes.
If I try and call equivalent methods directly on the Swift object:
myObject.addSubObject(subObject)
...I get a compiler error on the method call, because these generated accessors are not visible.
I've declared the relationship property as #NSManaged, as described in the documentation.
Or do I have to revert to Objective-C objects for data models with relationships?
As of Xcode 7 and Swift 2.0 (see release note #17583057), you are able to just add the following definitions to the generated extension file:
extension PersonModel {
// This is what got generated by core data
#NSManaged var name: String?
#NSManaged var hairColor: NSNumber?
#NSManaged var parents: NSSet?
// This is what I manually added
#NSManaged func addParentsObject(value: ParentModel)
#NSManaged func removeParentsObject(value: ParentModel)
#NSManaged func addParents(value: Set<ParentModel>)
#NSManaged func removeParents(value: Set<ParentModel>)
}
This works because
The NSManaged attribute can be used with methods as well as
properties, for access to Core Data’s automatically generated
Key-Value-Coding-compliant to-many accessors.
Adding this definition will allow you to add items to your collections. Not sure why these aren't just generated automatically...
Yeah that's not going to work anymore, Swift cannot generate accessors at runtime in this way, it would break the type system.
What you have to do is use the key paths:
var manyRelation = myObject.valueForKeyPath("subObjects") as NSMutableSet
manyRelation.addObject(subObject)
/* (Not tested) */
Core Data in Objective C automatically creates setter methods (1):
By default, Core Data dynamically creates efficient public and primitive get and set accessor methods for modeled properties (attributes and relationships) of managed object classes. This includes the key-value coding mutable proxy methods such as addObject: and removes:, as detailed in the documentation for mutableSetValueForKey:—managed objects are effectively mutable proxies for all their to-many relationships.
As things currently stand with Swift in Xcode6-Beta2, you'd have to implement those accessors yourself. For example if you have an unordered to-many relationship, from Way to Node, you'd implement addNodesObject like this:
class Way : NSManagedObject {
#NSManaged var nodes : NSSet
func addNodesObject(value: Node) {
self.mutableSetValueForKey("nodes").addObject(value)
}
}
Key here is that you'd have to use mutableSetValueForKey / mutableOrderedSetValueForKey / mutableArrayValueForKey. On these sets / arrays, you can call addObject and they'll be stored on the next flush.
You can just use a typed Set instead which is far easier. Following the example provided by #Nycen and #lehn0058 in the previous answer, you can just write:
extension PersonModel {
#NSManaged var parents: Set<ParentModel>?
}
And then use the insert and remove methods of the Set.
Expanding on the solution above one to many relationships are NSMutableSet so this allows you to directly add or remove the Person NSManagedObject to the Roles in this case a Person has one Role and Roles have many Person(s)
I have tested this solution under Xcode Beta-3 and this works!
This code takes out the Department to simplify showing the one to one and one to many code required to access Roles from a Person and Persons from a Role.
import CoreData
#objc(Person) class Person: NSManagedObject {
#NSManaged var name: String
//One to One relationship in your Model
#NSManaged var roles: Roles
}
#objc(Roles) class Roles: NSManagedObject {
#NSManaged var role: String
//One to Many relationship in your Model
#NSManaged var persons: NSMutableSet
}
extension Roles {
func addPersonsObject(value: Person) {
self.persons.addObject(value)
}
func removePersonsObject(value: Person) {
self.persons.removeObject(value)
}
func addPersons(values: [Person]) {
self.persons.addObjectsFromArray(values)
}
func removePersons(values: [Person]) {
for person in values as [Person] {
self.removePersonsObject(person)
}
}
}
As of Xcode 8 and Swift 3.0, Xcode now generates accessors for relationships. For example, I have an NSManagedObject class Store, that has a one to many relationship with Items; I've called that relationship SellsItems. The generated class for Store now has the following extension to add and remove from SellsItems. Adding or removing items to the relationship is as simple as calling these functions.
// MARK: Generated accessors for sellsItems
extension Store {
#objc(addSellsItemsObject:)
#NSManaged public func addToSellsItems(_ value: Item)
#objc(removeSellsItemsObject:)
#NSManaged public func removeFromSellsItems(_ value: Item)
#objc(addSellsItems:)
#NSManaged public func addToSellsItems(_ values: NSSet)
#objc(removeSellsItems:)
#NSManaged public func removeFromSellsItems(_ values: NSSet)
}
As you only need to set one side of a relationship for both to be set nowadays, it's particularly simple if you have a 1<->many relationship, e.g. a Department object has multiple Person objects, then you can just use:
aPerson.department = aDepartment
If you check you'll find that aDepartment.people (assuming that is the reciprocal relationship you've set up) will now contain the 'aPerson' Person object.
If the relationship is many<->many then one of the more complex solutions above appears necessary.
Let's say you have the following entities:
Person
Role
Department
In your Person entity, they have a to-many relationship with Role and to-one with Department. Your managed object might look something like this:
class Person : NSManagedObject
{
#NSManaged var roles : Array<Role>
#NSManaged var department : Department
}
Relationships with inverses (all should have them) only require one side to be set for the link to be established.
For example, if you set a Person's department property to a Department object, the inverse Department.people property would now also have this Person object contained inside.

Resources