fetch coredata with one to many relationship in swift
I solved the problem by using the NSPredicate
to connect the entity2 data with that attribute object from entity1 by using this line of code
let myFetch:NSFetchRequest<entity2> = entity2.fetchRequest()
let myPredicate = NSPredicate(format: "toEntity1-relationship == %@", (myTransferdObject?.name!)!)
myFetch.predicate = myPredicate
do {
usersList = try myContext.fetch(myFetch)
}catch{
print(error)
}
swift core data how should I save and fetch the one to many relationship and assign value to my viewModel entity
Rather creating relation like this you can do it saving the class id in teacher and student and get them on the basis of id like as:
class ClassEntity {
let id:String
var classTitleName: String
var classRank: Int
init (id:String,
classTitleName: String,
classRank: Int) {
self.id = id
self.classTitleName = classTitleName
self.classRank = classRank
}
}
then your teacher and student
class StudentEntity {
var classId:String
var studentName: String
var studentGender: String
init(classId:String,studentName: String,
studentGender: String){
self.classId = classId
self.studentName = studentName
self.studentGender = studentGender
}
}
class TeacherEntity {
var classId:String
var teacherName: String
var teacherGender: String
init(classId:String,teacherName: String,
teacherGender: String){
self.classId = classId
self.teacherName = teacherName
self.teacherGender = teacherGender
}
}
then create coredatastack
import Foundation
import CoreData
class CoreDataStack: NSObject {
private let modelName: String
lazy var managedContext: NSManagedObjectContext = {
return self.storeContainer.viewContext
}()
init(modelName: String) {
self.modelName = modelName
}
private lazy var storeContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: self.modelName)
container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
print("Unresolved error \(error), \(error.userInfo)")
}
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
return container
}()
func saveContext () {
guard managedContext.hasChanges else { return }
do {
try managedContext.save()
} catch let error as NSError {
print("Unresolved error \(error), \(error.userInfo)")
}
}
func updateContext() {
do {
try managedContext.save()
} catch let error as NSError {
print("Unresolved error \(error), \(error.userInfo)")
}
}
func clearChanges() {
managedContext.rollback()
}
func deleteReminder(reminder:Reminder) {
managedContext.delete(reminder)
updateContext()
}
func deleteNote(note:Note) {
managedContext.delete(note)
updateContext()
}
func deleteFromIds(selectedId:SubCategoryIds) {
managedContext.delete(selectedId)
updateContext()
}
}
then create a manager
import Foundation
import CoreData
class CoreDataManager {
static let shared = CoreDataManager()
private init(){}
lazy var coreDataStack = CoreDataStack(modelName: "YourModelName")
func allClasses() -> [ClassEntity] {
let fetechRequest: NSFetchRequest<ClassEntity> = ClassEntity.fetchRequest()
do {
let results = try coreDataStack.managedContext.fetch(fetechRequest)
return results
} catch {
}
return [ClassEntity]()
}
func allStudentOfClass(classId:String) -> [StudentEntity] {
let fetechRequest: NSFetchRequest< StudentEntity > = StudentEntity.fetchRequest()
fetechRequest.predicate = NSPredicate(format: "classId == %@",
argumentArray: [classId])
do {
let results = try coreDataStack.managedContext.fetch(fetechRequest)
return results
} catch {
}
return [StudentEntity]()
}
func allTeacherOfClass(classId:String) -> [TeacherEntity] {
let fetechRequest: NSFetchRequest<TeacherEntity> = TeacherEntity.fetchRequest()
fetechRequest.predicate = NSPredicate(format: "classId == %@",
argumentArray: [classId])
do {
let results = try coreDataStack.managedContext.fetch(fetechRequest)
return results
} catch {
}
return [TeacherEntity]()
}
}
how you can create a class
func createClass() {
let id = "Class 1" //or whatever you want to say here
let _class = Class(context: CoreDataManger.shared.coreDataStack.managedContext)
_class.id = id
_class.classTitleName = "Title"
_class.classRank = 12
CoreDataManger.shared.coreDataStack.saveContext()
}
same as how create teacher with this class
func addTeacher(classId:String) {
let _class = TeacherEntity(context: CoreDataManger.shared.coreDataStack.managedContext)
_class.classId = classId
_class.teacherGender = "Male"
_class.teacherName = "Alex"
CoreDataManger.shared.coreDataStack.saveContext()
}
One to Many Relationship Core Data fetch Data
toHobbies property is NSSet of Any so you need to typecast it or each x in your loop.
For predicates you can use key-paths like “toHobbies.location = %@“
Core Data fetch request one-to-many relationships with NSFetchedResultsController
If you want your FRC to filter Artists
by Genre
, you will need to add a relationship from Artist
to Genre
(the inverse of the artists
relationship on the Genre
entity). As a side note, you should almost always define an inverse for every relationship - there are very few situations where you are better off without an inverse.
I'm not sure whether you would regard each Artist
as having only one Genre
, or possibly having many - set the relationship as to-one
or to-many
as appropriate, and name it genre
or genres
accordingly. The predicate that is then required will be different in each case:
For to-one, you should use
NSPredicate(format:"genre == %@", chosenGenre)
and for to-many you would use
NSPredicate(format:"ANY genres == %@", chosenGenre)
(assuming you already have a reference, chosenGenre
, to the genre you wish to filter by. If you in fact have only the genre title, you would use:
NSPredicate(format:"genre.title == %@", chosenGenreTitle)
and for to-many you would use
NSPredicate(format:"ANY genres.title == %@", chosenGenreTitle)
Coredata update/insert for one to many relationship
First, you need to fetch the User.
Then, you have to choices:
user.devices.add(device)
or if it's two ways relationship, then you can call
device.user = user
How to fetch Specific Data in One to Many Core Data Relationships?
First...
Fix the naming of your relationships. As others have pointed out in comments on your other question, you have named them back to front: in the Person
entity, the to-many relationship to Account
should be named "accounts" not "person". Likewise in the Account
entity, the to-one relationship to Person
should be named "person" not "accounts".
Next...
Although you have defined the "accounts" relationship as to-many, the savePersonData
code in this question creates only one Account
and one Person
- but does not then set the relationship between them. (You can see this in your Output: each Account
has nil for its "accounts" relationship).
The code in your previous question did set the relationship (by adding the newAccount
to the relationship on the newPerson
object). In your code above you could use (after fixing the relationship names):
NSMutableSet *setContainer = [newDevice mutableSetValueForKey:@"accounts"];
[setContainer addObject:newAccount];
but with one-many relationships it is easier to set the inverse relationship:
[newAccount setValue:newDevice forKey:@"person"];
Next...
Your checkBalance
method correctly fetches any Person
objects whose "mobile_no" attribute matches. But your subsequent code then fetches ALL Account
objects - even if you had correctly set the relationship.
If you want only those Account
objects that are related to a given Person
, that is precisely what the "accounts" relationship represents. So you could just use that relationship to access the related Account
objects:
if(!([result count] == 0)) {
NSManagedObject *requiredPerson = (NSManagedObject *)result[0];
NSSet *temp = [requiredPerson valueForKey:@"accounts"];
for (NSManagedObject *object in temp) {
NSString *intValue = [object valueForKey:@"balance"];
NSString *alertString = [NSString stringWithFormat:@"%@",intValue];
[self displayAlertView:@"Available Balance" withMessage:alertString];
}
}
Alternatively, fetch the relevant Account
objects directly (without fetching the Person
object first) by specifying a predicate that traverses the relationship:
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
NSFetchRequest *newFetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Account"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"person.mobile_no = %@",self.textField.text];
[newFetchRequest setPredicate:predicate];
NSMutableArray *temp = [[managedObjectContext executeFetchRequest:newFetchRequest error:nil] mutableCopy];
for (NSManagedObject *object in temp)
{
NSString *intValue = [object valueForKey:@"balance"];
NSString *alertString = [NSString stringWithFormat:@"%@",intValue];
[self displayAlertView:@"Available Balance" withMessage:alertString];
}
Fetch one-to-many relationships in coreData and put it into a tableview
Apply the inverse relationship to User
in Task
and add an appropriate predicate
let username = "Foo"
let request: NSFetchRequest<Task> = Task.fetchRequest()
request.predicate = NSPredicate(format: "user.name == %@", username)
let dateSorter = NSSortDescriptor(key: "dueDate", ascending: false)
request.sortDescriptors = [dateSorter]
The way to create a new results controller is discouraged. Create the results controller lazily (once) and change predicate and sort descriptors dynamically.
swift core data relationship one to many for own entity
First update your class Managed Object class to use Sets instead of NSSet
import Foundation
import CoreData
extension Item {
@NSManaged var by: String?
@NSManaged var dead: NSNumber?
@NSManaged var descendants: NSNumber?
@NSManaged var id: NSNumber?
@NSManaged var isdeleted: NSNumber?
@NSManaged var score: NSNumber?
@NSManaged var text: String?
@NSManaged var time: NSDate?
@NSManaged var title: String?
@NSManaged var type: String?
@NSManaged var url: String?
@NSManaged var kids: Set<Item>
@NSManaged var parent: Item?
@NSManaged var parts: NSSet?
}
Secondly create a fetchRequest named getItemByID with expression
id == $ID
Third create a function to get item by id
func getItemById(ID:String) -> Item?
{
let fetchRequest : NSFetchRequest = self.managedObjectModel.fetchRequestFromTemplateWithName("getItemByID", substitutionVariables: ["ID": ID])!
do {
let fetchedResult = try managedObjectContext!.executeFetchRequest(fetchRequest) as! [Item]
if fetchedResult.count > 0 {
let result = fetchedResult[0]
return result
}
}
catch {
print("Could not fetch \(error)")
}
return nil
}
After that you have many options to set relations
a) you create relation after adding parent & child to coreData
func addChildToParent(childID:String,parentID:String)
{
if let childItem = getItemById(childID)
{
if let parentItem = getItemById(parentID)
{
parentItem.kids.insert(menuItemCatering)
childItem.parent = parentItem
}
}
saveContext()
}
Core data one to many relationship in swift
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.
Related Topics
Swift Optional Type: How .None == Nil Works
Swift 4.2 - _Shared Attribute Near Type
Accessing Global Variable in Swift
Extending Typed Array by Conforming to a Protocol in Swift 2
Saving Swifty JSON Array to User Defaults
Passing Data from Simple Nsview to Swiftui View
Handling an Attribute of an Xml Element in Swift
Swift: Search Bar Created at Auto Focus
Swift: Can Not Use Array Filter in If Let Statement Condition
Cannot Use Tabview on Swiftui, Watchos
Is Is Possible to Use Vapor 3 Postgres Fluent in a Standalone Script
How to Use Unsafemutablepointer<Opaquepointer> in Swift
Cabasicanimation Creates Empty Default Value Copy of Calayer
Ambiguous Use of Observe Firebase Db
How to Use Navigation in Alert Using Swiftui
Storing Array of Custom Objects in Userdefaults
Googlesignin - Always Return "The User Canceled the Sign-In Flow." in iOS 11