Swift One to many relationship - swift

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.

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

Could not cast value of type 'Swift.Optional<Swift.AnyObject>' to 'NSFetchRequest'

I always get this runtime error when I start my app using the CoreData framework. I followed precisely the instructions to set up the FetchRequest but I keep getting this error.
Could not cast value of type 'Swift.Optional<Swift.AnyObject>' (0x7feb68884da0)
to 'NSFetchRequest' (0x7feb68884e40).
Here I set up the FetchRequest in the View I want to use it.
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: NotificationsItem.getAllNotificationsItems()) var notificationsItems:FetchedResults<NotificationsItem>
And here is how I set it up in my NotificationsItem class
import CoreData
import Foundation
public class NotificationsItem: NSManagedObject, Identifiable {
#NSManaged public var createdAt: Date?
#NSManaged public var type: String?
}
extension NotificationsItem {
static func getAllNotificationsItems() -> NSFetchRequest<NotificationsItem> {
let request:NSFetchRequest<NotificationsItem> = NotificationsItem.fetchRequest() as! NSFetchRequest<NotificationsItem>
let sortDescriptor = NSSortDescriptor(key: "createdAt", ascending: true)
request.sortDescriptors = [sortDescriptor]
return request
}
}
Why is this code keep getting this error even if it works perfectly in the instructors copy?
Add this at the top of your NSManagedObject class definition
//This need to be declared here
#objc(NotificationsItem)
public class NotificationsItem: NSManagedObject, Identifiable {
...
And make sure to set the Codegen of the class to Manual/None

Swift CoreData: Unable to section tableView using sectionNameKeyPath with custom function

I am an extreme rookie with CoreData and I am attempting to section my tableView using fetchedResultsController using a custom function. My current implementation did not manage to section the tableView and I am still given just 1 section.
I refer to the following posts in my implementation: here and here. I also adopted the transient property.
I first create the NSManagedObject subclass using Xcode (Editor -> Create NSMangedObject Subclass) and added the var sectionIdentifier to return a custom string. Unfortunately, my frc returns only 1 section.
// from the Conversation+CoreDataProperties.swift file automatically generated by Xcode
import Foundation
import CoreData
extension Conversation {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Conversation> {
return NSFetchRequest<Conversation>(entityName: "Conversation")
}
#NSManaged public var conversationStartTime: Double
#NSManaged public var profileImage: NSData?
#NSManaged public var recipientID: String?
#NSManaged public var recipientUsername: String?
#NSManaged public var shoutoutID: String?
#NSManaged public var unreadMessagesCount: Int32
var sectionIdentifier: String? {
let presentTimestamp = NSDate().timeIntervalSince1970
if conversationStartTime < presentTimestamp - Double(Constants.PermissibleDurationInMinutes * 60) {
return "Expired Conversations"
} else {
return "Active Conversations"
}
}
}
//at VC
lazy var fetchedResultsController: NSFetchedResultsController<Conversation> = {
let context = CoreDataManager.shared.persistentContainer.viewContext
let request: NSFetchRequest<Conversation> = Conversation.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "conversationStartTime", ascending: true)]
let frc = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: "sectionIdentifier", cacheName: nil)
frc.delegate = self
do {
try frc.performFetch()
} catch let err {
print(err)
}
return frc
}()
Printing out the conversation entity in console returns this
<Conversation: 0x604000e93100> (entity: Conversation; id: 0xd000000003e00000 <x-coredata://91BC90B2-9A0C-45A7-9B82-844BE88BAFE0/Conversation/p248> ; data: {
conversationStartTime = "1521359598.88445";
profileImage = <ffd8ffe0 00104a46 49460001 02000001 00010000 ffed009c 50686f74 6f73686f 7020332e 30003842 494d0404 00000000 0080>;
recipientID = "-L7rvH71i-KUXvLQVDOh";
recipientUsername = Angemon;
sectionIdentifier = nil;
shoutoutID = "-L7rvH71i-KUXvLQVDOh";
unreadMessagesCount = 0; })
Somehow sectionIdentifier is always nil. Any advice why is this happening? At the end of the day, I want to divide my list of conversations into two sections, first section "Active Conversations" and second section "Expired Conversations" depending how long ago that conversation is.
UPDATED CODE:
// At Conversation+CoreDataProperties.swift
import Foundation
import CoreData
extension Conversation {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Conversation> {
return NSFetchRequest<Conversation>(entityName: "Conversation")
}
#NSManaged public var conversationStartTime: Double
#NSManaged public var profileImage: NSData?
#NSManaged public var recipientID: String?
#NSManaged public var recipientUsername: String?
#NSManaged public var shoutoutID: String?
#NSManaged public var unreadMessagesCount: Int32
#objc var sectionIdentifier: String {
willAccessValue(forKey: "sectionIdentifier")
let presentTimestamp = NSDate().timeIntervalSince1970
var text = ""
if conversationStartTime < presentTimestamp - Double(Constants.PermissibleDurationInMinutes * 60) {
text = "Expired Conversations"
} else {
text = "Active Conversations"
}
didAccessValue(forKey: "sectionIdentifier")
return text
}
}
//At VC
lazy var fetchedResultsController: NSFetchedResultsController<Conversation> = {
let context = CoreDataManager.shared.persistentContainer.viewContext
let request: NSFetchRequest<Conversation> = Conversation.fetchRequest()
request.sortDescriptors = [
NSSortDescriptor(key: "conversationStartTime", ascending: false)
]
let frc = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: "sectionIdentifier", cacheName: nil)
frc.delegate = self
do {
try frc.performFetch()
} catch let err {
print(err)
}
return frc
}()
At my CoreData model screen:
This is a continuation on the answer from #Sandeep since I wanted to see if it was possible to group by a transient property without having a sort descriptor with it since one of your links implied it should be possible to do so.
After some experimentation I managed to conclude that it is possible if you fulfil two conditions:
You need a sort descriptor (and it needs to be the first one) on the persistent attribute you use for calculating your transient attribute
The sort order of the sort descriptor needs to match the transient attribute.
(I assume this applies for when you access multiple persistent attributes as well for your transient attribute, sort descriptors for all of them and sort order needs to match but I haven't tested)
As I see it you fulfil the first one but not the second one since your sort descriptor has ascending = true but since "Active" comes before "Expired" you have an implicit descending sort order for sectionIdentifier.
Here is a silly example I made with some existing code since I had some test data available for it. As you can see I divide the days of the month into three sections so that the will show up in reverse chronological order.
#objc var silly: String {
willAccessValue(forKey: #keyPath(silly))
var result: String = "In between"
let component = Calendar.current.component(Calendar.Component.day, from: date)
if component <= 13 {
result = "Last"
} else if component > 20 {
result = "At the Top"
}
didAccessValue(forKey: #keyPath(silly))
return result
}
And when setting up my fetched result controller I do
...
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(Location.date), ascending: false)]
let fetchedResultController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: managedObjectContext,
sectionNameKeyPath: #keyPath(Location.silly),
cacheName: "Locations")
The will/didAccessValue(...) was necessary to handle insert and updates of new objects after the first execute of the request, otherwise the attribute is set to nil.

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!

Populating coredata many-to-many relationship

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)