Populating coredata many-to-many relationship - swift

I have a Worker and Event Entity. I'm having trouble when creating a new worker for a Event the worker entity has a corresponding event but the event entity is not saving the workers.(shouldn't this be happening automatically when setting the inverse option)
class Event: NSManagedObject {
#NSManaged var id: NSNumber
#NSManaged var name: String
#NSManaged var startTime: String
#NSManaged var endTime: String
#NSManaged var workers: NSMutableSet
func addWorker(name: String, startTime: String, endTime: String) {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
var worker: Worker = NSEntityDescription.insertNewObjectForEntityForName("Worker", inManagedObjectContext: managedContext) as! Worker
worker.name = name
worker.startTime = startTime
worker.endTime = endTime
worker.id = Worker.getNewWorkerId()
worker.events.addObject(self)
var error: NSError?
if !managedContext.save(&error) {
println("Could not save \(error), \(error?.userInfo)")
}
}
}

You must also define the relationship to be "To Many" on both ends of the relation. To define the relationships as such, select the relationship in question and on the right-pane set 'Type' to 'To Many'.
Additionally, your many to many attributes in your subclasses of NSManagedObject should be of type NSSet and NSOrderedSet, not NSMutableSet. See the documentation here: Managed Object Accessor Methods.
If you want to add any additional information about the relationship an 'associative' or 'join' entity will be required.
(CoreData Many-To-Many Relationships)

Related

Swift CoreData error unrecognized selector sent to instance when insert row

I am getting this error Thread 1: Exception: "-[Tasks initWithCoder:]: unrecognized selector sent to instance 0x60000034da40" whenever I try to load my core data object.
I have two entities (Tasks, Goal) with inverse many to many relationship.
The goal entity has an attribute of transformable with a custom class [NSManagedObject] of task. I think this creates an issue when loading. but surprisingly when I save my context it doesn't crash
Here us my subclass codegen of goal entity
extension Goal {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Goal> {
return NSFetchRequest<Goal>(entityName: "Goal")
}
#NSManaged public var date: String?
#NSManaged public var goalTasks: [NSManagedObject]?
#NSManaged public var isComplete: Bool
#NSManaged public var name: String?
#NSManaged public var nsdate: Date?
#NSManaged public var tasks: NSSet?
}
// MARK: Generated accessors for tasks
extension Goal {
#objc(addTasksObject:)
#NSManaged public func addToTasks(_ value: Tasks)
#objc(removeTasksObject:)
#NSManaged public func removeFromTasks(_ value: Tasks)
#objc(addTasks:)
#NSManaged public func addToTasks(_ values: NSSet)
#objc(removeTasks:)
#NSManaged public func removeFromTasks(_ values: NSSet)
}
Here us my subclass codegen of tasks entity
import Foundation
import CoreData
extension Tasks {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Tasks> {
return NSFetchRequest<Tasks>(entityName: "Tasks")
}
#NSManaged public var date: String?
#NSManaged public var importValue: Int16
#NSManaged public var isComplete: Bool
#NSManaged public var list: String?
#NSManaged public var name: String?
#NSManaged public var nsdate: Date?
#NSManaged public var goals: NSSet?
}
// MARK: Generated accessors for goals
extension Tasks {
#objc(addGoalsObject:)
#NSManaged public func addToGoals(_ value: Goal)
#objc(removeGoalsObject:)
#NSManaged public func removeFromGoals(_ value: Goal)
#objc(addGoals:)
#NSManaged public func addToGoals(_ values: NSSet)
#objc(removeGoals:)
#NSManaged public func removeFromGoals(_ values: NSSet)
}
here is how I am inserting a new goal object
#IBAction func setGoal(_ sender: Any) {
// Adding a task to the array
let df = DateFormatter()
df.dateFormat = "dd-MM-yyyy" // assigning the date format
let goalVC = GoalViewController()
let now = df.string(from: Date())
let newGoal = NSEntityDescription.insertNewObject(forEntityName: "Goal", into: context) as! Goal
newGoal.setValue(goalTitle.text!, forKey: "name")
newGoal.setValue(false, forKey: "isComplete")
newGoal.setValue(goalDate, forKey: "nsdate")
newGoal.setValue(now, forKey: "date")
newGoal.setValue(goalSubTasks, forKey: "goalTasks")
do {
try
context.save()
homeVC.loadGoals()
print(newGoal)
} catch {
print("Problem while saving")
}
self.dismiss(animated: true, completion: nil)
}
and here the app crashes when this function is called where I load my load my goals into allGoals (array of [NSManagedObject])
func loadGoals(){
allGoals.removeAll()
let requestGoal = NSFetchRequest<NSFetchRequestResult>(entityName: "Goal")
do {
allGoals = try context.fetch(requestGoal) as! [NSManagedObject]
print("loadTasks() fired!")
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
I think the issue lies in the Transformable goalTasks attribute in Goal Entity but I am not sure what to do with it. any help is appreciated!
The error arises because you have specified the goalTasks attribute to be Transformable with custom class [NSManagedObject]. When you fetch the Goals, CoreData uses the default value transformer to create an array of objects, which results in it calling the initWithCoder method of your Tasks class. Since that method doesn't exist, you get the crash.
However, you do not need to use the goalTasks attribute: you have a tasks relationship defined. To assign certain Tasks to certain Goals, you use the methods identified in the corresponding class definitions, eg. addToTasks and addToGoals, ie:
certainGoal.addToTasks(certainTask)
or
certainTask.addToGoals(certainGoal)
Note you need only use one of these methods. Because the relationships are defined as inverses, CoreData will automatically update the inverse.
#pbasdf answer is correct but has a limitation
cause of the goals are unique you cant add goals twice or more with a relationship.
in this case you have to add an intermediate entity which will hold the goals
in my case:
i have an app in which you have to go from start to 2 waypoints and back to start
the problem here ist to add the start-waypoint at last position
this does not work with pbasdf answer... cause you cant add them twice or more

Looping Core Data Objects outside a Table/ Collection View

I want to loop through the objects in CoreData without using a tableView or CollectionView... But using a ViewController
I tried something like:
for var i = 0; i < numberOfExerciseItems; i++ {
let exerciseItemsfromDay = fetchedResultController.objectAtIndexPath(i) as! ExerciseItemModel
}
This obviously doesn't work since it is not of NSIndexPath type as you'd get in a table or CollectionView. Are there ways to do this outside a Table / Collection View? Thanks for the ideas in advance.
Edit
From this answer, I'd want to access an entity from an item in the items array in the loop :
let request = NSFetchRequest(entityName: "ExerciseItemModel")
let items = (try? context.executeFetchRequest(request)) as? [ExerciseItemModel] ?? []
In this loop
items.forEach {
print(items) //Displays all the objects in the console.
print(items[1].attribute //Throws the error Value of type'AnyObject' has no member 'attribute' and I cannot access an attribute from the items array . Not sure why!
}
Edit My ExerciseModel Class
import Foundation
import CoreData
#objc(ExerciseItemModel)
class ExerciseItemModel: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
}
extension ExerciseItemModel {
#NSManaged var exerciseType: String?
#NSManaged var exerciseName: String?
#NSManaged var durationOrSets: String?
#NSManaged var distanceOrReps: String?
#NSManaged var weight: String?
#NSManaged var backgroundImage: NSData?
#NSManaged var dayID: String?
#NSManaged var date: NSDate?
}
let request = NSFetchRequest(entityName: "ExerciseItemModel")
let items = (try? context.executeFetchRequest(request)) as? [ExerciseItemModel] ?? []
items.forEach {
// Do your stuff
}
A small example. Not exactly an answer so I'll delete it when a proper answer has been posted. This example will not show you any error messages if there are any, just keep that in mind.
Edit:
You're using forEach() incorrectly:
items.forEach {
print($0) // Print's the object in items
print($0.attribute) // Should print the attribute
}
print(items[0].attribute) // Probably same error, but it should be used outside the loop

Core Data Join Table Many to Many Relationship - Object Creation

I want to create a join table (BadgeProfile) for 2 entities: HubProfile & Badge. See pic below
Issue 1: I want to know how to create a "BadgeProfile" object. This object should map to one "HubProfile" and one "Badge" Object
Issue 2: Once the "BadgeProfile" object is created which can later be accessed by both "HubProfile" & "Badge". (example: HubProfile.BadgeProfile) How do I do that?
Below are the classes for the entities:
HubProfile
class HubProfile: NSManagedObject {
#NSManaged var id: NSNumber?
#NSManaged var name: String?
#NSManaged var hubBadgeProfiles: NSOrderedSet?
}
Badge
class Badge: NSManagedObject {
#NSManaged var id: NSNumber?
#NSManaged var name: String?
#NSManaged var score: NSNumber?
#NSManaged var badgeProfiles: NSOrderedSet?
}
BadgeProfile
class BadgeProfile: NSManagedObject {
#NSManaged var id: NSNumber?
#NSManaged var badge: Badge?
#NSManaged var hubProfile: HubProfile?
}
P.S: I am aware that I don't need to create a Join Table and can go with a Many to Many relationship between HubProfile <<-- -->> Badge. But I want to create the join table as it will make it very easy to interact with the backend DB.
To create the join table object:
Create it the same way you would create any other entity, e.g. via
NSEntityDescription.insertNewObjectForEntityForName(...)
Then just add the objects you want to link.
badgeProfile.hubProfile = hubProfile
badgeProfile.badge = badge
I would advise not to use ordered sets. They are complicated and error prone. The above, for example, becomes a real headache. Instead, use a number attribute in the BadgeProfile entity to keep track of the order yourself.
To access the joined object
Nothing special here. Get all badges for a profile:
profile.hubBadgeProfiles.map { $0.badge! }
(I think your attribute name is poorly chosen. I suggest badges, and the equivalent on the other side, profiles for the Badge entity.)
Get a specific badge:
profile.badges.filter { $0.badge!.name == "Gold" }.first as? Badge
Use the analog pattern to go in the other direction.
I think I have found the solution:
Creating Sample Objects:
func loadBadgeProfilesPart2() //Second Way to Create BadgeProfile
{
let hubProfileEntity = NSEntityDescription.entityForName("HubProfile", inManagedObjectContext: managedContext)
let hubProfile1 = HubProfile(entity: hubProfileEntity!, insertIntoManagedObjectContext: managedContext)
hubProfile1.id = 5
hubProfile1.name = "EFG"
let badgeEntity = NSEntityDescription.entityForName("Badge", inManagedObjectContext: managedContext)
let badge1 = Badge(entity: badgeEntity!, insertIntoManagedObjectContext: managedContext)
badge1.id = 5
badge1.name = "Polite"
let badgeProfileEntity = NSEntityDescription.entityForName("BadgeProfile", inManagedObjectContext: managedContext)
let badgeProfile1 = BadgeProfile(entity: badgeProfileEntity!, insertIntoManagedObjectContext: managedContext)
badgeProfile1.id = 3
let badgeProfile2 = BadgeProfile(entity: badgeProfileEntity!, insertIntoManagedObjectContext: managedContext)
badgeProfile2.id = 4
let badgeProfileSet: NSMutableOrderedSet = [] //Add to an NSOrderedSet
badgeProfileSet.addObject(badgeProfile1)
badgeProfileSet.addObject(badgeProfile2)
hubProfile1.hubBadgeProfiles = badgeProfileSet //Assign BadgeProfile to HubProfile
badge1.badgeProfiles = badgeProfileSet //Assign BadgeProfile to Badge
do
{
try! self.managedContext.save()
}
}
Fetching the Objects in TableView
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! BadgeProfileViewCell!
configureCell(cell, indexPath: indexPath)
return cell
}
func configureCell(cell: BadgeProfileViewCell, indexPath: NSIndexPath) //Showing in TableView
{
let badgeProfile = fetchedResultsController.objectAtIndexPath(indexPath) as! BadgeProfile
cell.nameLabel?.text = badgeProfile.hubProfile?.name
cell.badgeLabel?.text = badgeProfile.badge?.name
}
This seems to be working.

CoreData - One-to-many relationship

I'll post the following code then explain my struggle
This function saves a day (like sunday, monday, tuesday, etc):
func appendDaysToArray() {
let dayLabel = dayName.text
daysArray.append(dayLabel)
let dayEntity = NSEntityDescription.entityForName("TrainingDay", inManagedObjectContext: moc!)
let trainingday = TrainingDay(entity: dayEntity!, insertIntoManagedObjectContext: moc)
trainingday.day = dayName.text
var error: NSError?
moc?.save(&error)
if let err = error {
var status = err.localizedFailureReason
println("\(status)")
} else {
println("Day #\(dayName.text) saved successfully!")
}
}
and this one saves details as a name, a number of sets and a number of repetitions (like gym exercises):
func appendTrainingDetails () {
let nameLabel = exerciseName.text
namesArray.append(nameLabel)
let numberOfSets = setsNumber.text?.toInt()
setsArray.append(numberOfSets!)
let numberOfReps = repsNumber.text?.toInt()
repsArray.append(numberOfReps!)
let detailsEntity = NSEntityDescription.entityForName("TrainingDetails", inManagedObjectContext: moc!)
let trainingdetails = TrainingDetails(entity: detailsEntity!, insertIntoManagedObjectContext: moc)
trainingdetails.exerciseName = exerciseName.text
trainingdetails.setsNumber = setsNumber.text!
trainingdetails.repsNumber = repsNumber.text!
var error: NSError?
moc?.save(&error)
if let err = error {
var status = err.localizedFailureReason
println("\(status)")
} else {
println("Exercise: #\(exerciseName.text) saved successfully!")
println("Number of sets: #\(setsNumber.text) saved successfully!")
println("Number of reps: #\(repsNumber.text) saved successfully!")
}
}
My app is working ok, but what I actually need is this: for each DAY, I will have multiple exerciseNames, setsNumber and repsNumber. I set a one-to-many relationship, but I don't know how to attribute the TrainingDetails to each day in the daysArray.
Here are my 2 models:
import Foundation
import CoreData
class TrainingDay: NSManagedObject {
#NSManaged var day: String
#NSManaged var relationship1: NSSet
}
and
import Foundation
import CoreData
class TrainingDetails: NSManagedObject {
#NSManaged var exerciseName: String
#NSManaged var repsNumber: String
#NSManaged var setsNumber: String
#NSManaged var relationship2: TrainingDay
}
Later, I'll have a button for each day and, when pressed, they will update a tableView with the list of exercises for that specific day. That's why I need to set this one-to-many relationship.
How can I achieve this?
Sorry for any mistakes. Thanks in advance!!
The documentation for what you want to do is in this link, under the heading "To-Many Relationships".
Here is a short example. First, I recommend changing the names of your relationships to something more intuitive. It will really help:
class TrainingDay: NSManagedObject {
#NSManaged var day: String
#NSManaged var trainingDetails: NSSet
}
class TrainingDetails: NSManagedObject {
// ... other stuff here
#NSManaged var trainingDay: TrainingDay
}
Make sure any changes you make to the code are also made in the model graph. Make sure that the relationships are configured to be the inverse of each other in the model graph.
You can set the TrainingDay for a given TrainingDetails like this:
// This assumes you've created a TrainingDay called "trainingDay1", and a
// TrainingDetails object called "details".
details.trainingDay = trainingDay1
Core Data takes care of creating the inverse relationship as well, so the trainingDay1 object will automatically add details to its trainingDetails set.
If you are trying to add objects to the trainingDetails set in TrainingDay, you need to use the mutableSetValueForKey: method described in the documentation that I linked to. Basically, it looks like this:
var details = trainingDay1.mutableSetValueForKey("trainingDetails")
details.addObject(newTrainingDetails)
The mutableSetValueForKey: creates a proxy object. Any changes made to the set that it returns are effective on the set you are trying to modify.
Hope this helps!

Swift One to many relationship

I have two entities, User and Task. They are linker by a One to Many relationship, this is what they look like
import Foundation
import CoreData
#objc(User)
class User: NSManagedObject {
#NSManaged var id: String
#NSManaged var name: String
#NSManaged var task: NSSet
}
and
import Foundation
import CoreData
#objc(Task)
class Task: NSManagedObject {
#NSManaged var context: String
#NSManaged var date: String
#NSManaged var detail: String
#NSManaged var folder: String
#NSManaged var id: String
#NSManaged var responsable: String
#NSManaged var status: String
#NSManaged var summary: String
#NSManaged var user: User
}
The relationship on the xcdatamodel is:
relationship: user, destination: User, Inverse: task for Task
relationship: task, destination: Task, Inverse: user for User
I have my fetchedResultController that let me doing my requests on the Task table:
func getFetchedResultsController(String) -> NSFetchedResultsController{
frc = NSFetchedResultsController(fetchRequest: taskFetchRequest(folder), managedObjectContext: context!, sectionNameKeyPath: nil, cacheName: nil)
return frc
}
func taskFetchRequest(String) -> NSFetchRequest {
//on choisit sur quel Entity on travaille
let fetchRequest = NSFetchRequest(entityName: "Task")
//on Choisit sur quel attribut on place l'Order By. Ici sur Summary en Ascendant
let predicate = NSPredicate(format: "folder = %#", folder)
let sortDescriptor = NSSortDescriptor(key: "summary", ascending: true)
fetchRequest.predicate = predicate
fetchRequest.sortDescriptors = [sortDescriptor]
return fetchRequest
}
On both entity, I have one common attribute, id of User and responsable of Task are the same things, What I need to do is, for a task that has 135482dfsq4g1responsible, printing the corresponding name of the User entity (so the id 135482dfsq4g1)
I have already done some tests but nothing effective, like this:
var task : Task?
var user : User?
if(task?.responsable == user?.id){
OwnerTextField.text = user?.name
}else{
println(task?.responsable)
println(user?.id)
OwnerTextField.text = ""
}
task?.responsable is found but not user?.id, so I can't compare:
Optional("76bbfe695318d471a541bc3333e58eea28acae54")
nil
Any help would be appreciated.
Regards.
Just remove the responsible property and rename the relationship user as responsible. You will get
NSManaged var responsable: User
and getting responsible?.name (and responsible?.id if you really need it) will be straightforward.