Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Core data one to many relationship in swift

I have been searching for days on how to work with one to many relationships. But I can't get it figured out.

I am working on a app which stores the grades of a subject. So one subject has a to-many relationship with grades. All the data is currently stored in a array.

var vakken: [(naam:String, voorkeurGemiddelde:Double, wegingGemiddelde:Double, cijfer:[Double], weging:[Double], gemiddelde:Double)] = []
Where the the [Double]'s - Cijfer and weging- are attributes of a grade.

dataModel

I currently have the following for saving the data:

 let context = AppDelegate().managedObjectContext

    // Create subject
    let entitySubject = NSEntityDescription.entityForName("Subject", inManagedObjectContext: context)
    let Subject = NSManagedObject(entity: entitySubject!, insertIntoManagedObjectContext: context)

    // Populate subject
    Subject.setValue(vakken.last!.naam, forKey: "name")
    Subject.setValue(vakken.last!.voorkeurGemiddelde, forKey: "voorkeurGemiddelde")
    Subject.setValue(vakken.last!.wegingGemiddelde, forKey: "wegingNP")
    Subject.setValue(vakken.last!.gemiddelde, forKey: "gemiddelde")

    for var j = 0; j < vakken.last!.cijfer.count-1; j++ {

        let Grade = NSEntityDescription.insertNewObjectForEntityForName("Grade", inManagedObjectContext: context)

        Grade.setValue(vakken.last!.cijfer[j], forKey: "cijfer")
        Grade.setValue(vakken.last!.weging[j], forKey: "weging")

        Subject.setValue(NSSet(object: Grade), forKey: "grade")
    }

    do{
        try context.save()
    }catch{
        print("error")
    }

I am using the data in a tableView. I have seen in some tutorials that the use: var vakken = [NSManagedObject]() and then use it in a tableview.
1. But is this also possible with the model I have, and should I use it?

I first thought was to just save the data when the app terminates, so I would have the code in the app delegate, and then just retrive it when the app starts.

2. But how would I get the array to the appdelegate and would this be smart?

And for fetching the data I have this:

  let fetchRequest = NSFetchRequest(entityName: "Subject")

//        // Add Sort Descriptor
//        let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)               
//        fetchRequest.sortDescriptors = [sortDescriptor]


        let context = Appdelegate().managedObjectContext

        // Execute Fetch Request
        do {
            let result = try context.executeFetchRequest(fetchRequest)
            print(result)
        } catch {
            let fetchError = error as NSError
            print(fetchError)
        }

3. But how would I get from the result my array back or if I use NSManagedObject how can I refer to the attributes and in most particular the grades.

I also sort my array by name which can be A-Z or Z-A, and I sort by average which can be 10-1 or 1-10. I have the code to sort it in my array.
4. But I don't know how to sort this with core data.

I have also seen a tutorial where they made a class for the attributes link. So I made one my self:

    import UIKit
import CoreData
import Foundation

class Subject: NSManagedObject {
    @NSManaged var gemiddelde: Double
    @NSManaged var name: String
    @NSManaged var voorkeurGemiddelde: Double
    @NSManaged var wegingNP: Double
    @NSManaged var Grades: NSSet
}

class Grade: NSManagedObject {
    @NSManaged var cijfer: Double
    @NSManaged var weging: Double
    @NSManaged var subject: Subject
}

extension Subject {
    func addTagObject(value:Grade) {
        let items = self.mutableSetValueForKey("grade");
        items.addObject(value)
    }

    func removeTagObject(value:Grade) {
        let items = self.mutableSetValueForKey("grade");
        items.removeObject(value)
    }
}

5. But I don't know if I need it nor how to use it?

If you have any advise, tips, know good tutorials or know one of these questions. Your help would be appreciated.

If you need any more information just let me know. :)

Update:

I have this code to get the object of the subject witch is clicked.

if let indexPath = self.tableView.indexPathForSelectedRow {
                let object = self.fetchedResultsController.objectAtIndexPath(indexPath)
                let controller = segue.destinationViewController as! VakTableViewController
                controller.vak = object as! Subject

            }

And this is the code on the receiving end:

 var vak: Subject! {
    didSet {
        // Update the view.
        cijfers = vak.valueForKeyPath("grades.cijfer")!.allObjects as! [Double]
        wegingen = vak.valueForKeyPath("grades.weging")!.allObjects as! [Double]
        self.tableView.reloadData()
    }
}

var cijfers: Array<Double>!
var wegingen: Array<Double>!

6. But how can i delete on of the grades?

I have tried this:

// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if editingStyle == .Delete {
        if cijfers.count != 1 {
            let entityGrade = NSEntityDescription.entityForName("Grade", inManagedObjectContext: mangedobjectcontext)
            let grade = Grade(entity: entityGrade!, insertIntoManagedObjectContext: mangedobjectcontext)

            grade.cijfer = cijfers[indexPath.row]
            grade.weging = wegingen[indexPath.row]
            vak.removeGradeObject(grade)
            do{
                try mangedobjectcontext.save()
            }catch{
                print("error")
            }
            cijfers.removeAtIndex(indexPath.row)
            wegingen.removeAtIndex(indexPath.row)
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
            print(vak.valueForKeyPath("grades.cijfer")?.allObjects)
        }
    }
}

But this doesn't work.

7. And how can i save the vak(subject) to the context when i just change own of the variable?

like this: vak.gemiddelde = newgemiddelde

like image 850
Cing Avatar asked Feb 12 '16 21:02

Cing


People also ask

What are relationships in Core Data?

Inverse relationships enable Core Data to propagate change in both directions when an instance of either the source or destination type changes. Every relationship must have an inverse. When creating relationships in the Graph editor, you add inverse relationships between entities in a single step.

Is Core Data thread safe in Swift?

Core Data is designed to work in a multithreaded environment. However, not every object under the Core Data framework is thread safe. To use Core Data in a multithreaded environment, ensure that: Managed object contexts are bound to the thread (queue) that they are associated with upon initialization.

What is Core Data in Swift iOS?

Overview. Use Core Data to save your application's permanent data for offline use, to cache temporary data, and to add undo functionality to your app on a single device. To sync data across multiple devices in a single iCloud account, Core Data automatically mirrors your schema to a CloudKit container.

What is NSManagedObjectContext in Core Data?

An object space to manipulate and track changes to managed objects.


1 Answers

To address your questions directly:

I am using the data in a tableView. I have seen in some tutorials that the use: var vakken = [NSManagedObject]() and then use it in a tableview. Is this also possible with the model I have, and should I use it

It is certainly possible, but it would probably be better to use an NSFetchedResultsController (see the Apple Docs). This is specifically designed to make to easy to populate a tableView with data from Core-Data.

I first thought was to just save the data when the app terminates, so I would have the code in the app delegate, and then just retrive it when the app starts. How would I get the array to the appdelegate and would this be smart?

I wouldn't populate/save the array in the App Delegate. Some people follow Apple's template projects, which build the CoreData stack in the App Delegate; others have a separate class that they instantiate to manage the stack. From the look of your code, it currently uses the former. Your view controllers can then get the NSManagedObjectContext from the App Delegate using:

let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context = appDelegate.managedObjectContext

(As beyowulf points out, you should not use AppDelegate().managedObjectContext as that creates a new instance, rather than referring to the existing instance). Once they have a context, your view controllers can fetch the data they need, add or update existing records, etc.

But how would I get from the result my array back or if I use NSManagedObject how can I refer to the attributes and in most particular the grades.

result is an array of NSManagedObjects. You can obtain the attribute values using valueForKey, the "read" equivalent of setValue:(_, forKey:):

let firstObject = result[0]
let firstObjectName = firstObject.valueForKey("name")

In the same way, the grades can be obtained with:

let firstObjectGrades = firstObject.valueForKey("grades")

There are better ways, though: see your final question below.

I also sort my array by name which can be A-Z or Z-A, and I sort by average which can be 10-1 or 1-10. I have the code to sort it in my array. But I don't know how to sort this with core data.

It is easiest to sort the data when you fetch it. To do this specify a NSSortDescriptor (see the Apple Docs) for the fetch:

let fetchRequest = NSFetchRequest(entityName: "Subject")
fetchRequest.sortDescriptors = [NSSortDescriptor(key:"name", ascending:true)

I have also seen a tutorial where they made a class for the attributes. I don't know if I need it nor how to use it?

Yes, use it. It will make life so much easier. Rather than write your own class definition, use the Xcode menu option "Create NSManagedObject subclasses" in the data model editor. It will create the class code for you and will also set each Entity to use the corresponding class. (If you wish to stick with your own code, you will need to amend the "class" for each entity in the data model editor).

Once you have the subclasses properly defined, you can then refer to the attributes and relationships using dot notation, rather than needing to use valueForKey and setValueForKey:

So:

let Subject = NSManagedObject(entity: entitySubject!, insertIntoManagedObjectContext: context)

will become:

let subject = Subject(entity: entitySubject!, insertIntoManagedObjectContext: context)

(Note, variables should begin with lowercase - Classes and Entity names begin with uppercase).

Grade.setValue(vakken.last!.cijfer[j], forKey: "cijfer")

becomes:

grade.cijfer = vakken.last!.cijfer[j]

and

let firstObjectGrades = firstObject.valueForKey("grades")

becomes:

let firstObjectGrades = firstObject.grades

Also, the addTagObject and removeTagObject functions will make it easier to manage the to-many relationship. Your code currently has:

Subject.setValue(NSSet(object: Grade), forKey: "grade")

This will replace any existing grades for that Subject with the Grade object: it doesn't add it to the existing. In fact, for one-many relationships it's far easier to manage the inverse (to-one) relationship:

grade.subject = subject

CoreData will handle the inverse (to-many) relationship automatically for you.

How can i delete one of the grades?

First, don't build separate arrays for each of the Grades attributes: just have one array for the Grades:

var vak: Subject! {
    didSet {
        // Update the view.
        grades = vak.grades.allObjects as! [Grade]
        self.tableView.reloadData()
    }
}

var grades: Array<Grade>!

You can get the attributes easily enough whenever you need them. For example, in your cellForRowAtIndexPath you might have something like this:

let grade = grades[indexPath.row]
let cijfer = grade.cijfer
let weging = grade.weging
// populate cell labels etc using "cijfer" and "waging"

Your commitEditingStyle can then easily find which Grade is being deleted, and handle accordingly:

override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if editingStyle == .Delete {
        let grade = grades[indexPath.row]
        // either remove the Grade from your Subject (which leaves
        // the Grade "orphaned"):
        grade.subject = nil
        // or (probably better) completely delete the grade:
        managedobjectcontext.deleteObject(grade)
        // then remove it from the "grades" array:
        grades.removeAtIndex(indexPath.row)
        // and finally delete the corresponding table view row
        tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        // wrap up by saving the changes to the context:
        do{
            try mangedobjectcontext.save()
        }catch{
            print("error")
        }
    }
}

How can i save the vak(subject) to the context when i just change one of the variable?

Whenever you save the context, it saves ALL the changes to any objects registered with it (inserted into it or fetched using it). So, even if the only change you have made is to amend one attribute of one Subject, just call save() on the context.

like image 87
pbasdf Avatar answered Oct 07 '22 13:10

pbasdf