Manipulate Relationships in Core Data - ios

I have a NSManagedObject subclass defined like this:
extension Item {
#NSManaged var name: String
#NSManaged var parent: Item
#NSManaged var children: [Item]
}
In order to set the 'parent' attribute, I use this:
anItem.parent = aParent
And I know it will automatically append anItem to aParent.children too.
However, I would like the child item to be added at a specific position.
This idea is to have a sorted array of children.
I know I could simply do this:
aParent.childen.insert(anItem, atIndex: specificPosition)
But is there any method to override or operator to overload in order to automatically achieve that ?

Inserting the child at a particular position will not ensure the same sort order later when you fetch.
You will need an additional attribute to define the sort order during the fetch.
Have a look at this answer

A couple misconceptions here:
CoreData to-many relationships are not of type Array, they are Set or NSSet. This can be verified by having your subclass generated by Xcode.
It is possible to to have an ordered relationship - just take a look at the relationship inspector in your Core Data Model. This will change it to a NSOrderedSet
MAJOR CAVEAT to (2) - The order is determined exclusively by the order that you add them - see this link for more info.
It is much more memory intensive to store these as ordered, if you can, have an attribute you can use to order them after fetching.

Related

Best way to store and refrence lots data in Swift for use in a UITableView

This is a bit confusing so I apologise. But:
I am trying to make an app where the user has the ability to add items to a list of individual items that will be displayed back to them(Something along the lines of a todo list app) as a table view.
However, I have hit a roadblock I need to store several different bits of data for each item(Some Strings, Some ints and a date) in the list.
I think that a class(or struct) would be the best way to do this where an instance of the class holds the information need for each item and then the name of that instance is stored in a list so it can be accessed via the indexPath in the table view.
However, I don't know how I am going to make a new instance of the class for every item because the app could have hundreds of individual items.
I'm sorry, this is so confusing and any help would be appreciated! Feel free to ask for more info
Edit: what I am looking for and I'm sure there's a stupidly easy way of doing it but I'm try to work out how to create an instance of a class when the name of the class is stored in a variable. Ecencialy I want the instance of the class to store the item. To be created when the user inputs the item to be added to the table.
Eg. They enter an item. item1 and the other data that goes along with then I want to be able to store that in instance of the item class but I don't know how to make the name of that instance because the name I want which is item 1 is stored in a variable.
Sorry that's so confusing that's the reason I need help
So first: You can't store the Name of a Class in a Variable and use this variable to get a new instance of a class.
What you need is an Array containing all the different Items. This array is unlimited so you can store as many items in it as you like and you don't need to set a name for each of these Instances.
First create an empty array containing all Items as a property:
var items : [Item] = []
Second call the function populateItemArray() in your viewDidLoad():
private func populateItemArray() {
// Populate the items Array (Are you using CoreData, Realm, ...?)
}
Third, use the Item Array to populate the TableView.
REMEMBER: If your only using one section in your tableView, indexPath.row is always equal to the corresponding item in the array.
E.G. items[indexPath.row]
Hope this helps you!
UPDATE:
Look at this example struct Item. As you can see you can also store a date in it:
struct Item {
// First create all the properties for your struct.
var data1: String
var data2: Int
var data3: String
// You can create properties of any type you want, also of type date.
var dateOfCreation : Date
// Then implement all the methods of your custom Struct Item.
func setDate(with date : Date) {
self.dateOfCreation = date
}
func returnDate() -> Date {
return self.dateOfCreation
}
}

IOS/Core-Data: add many to many relationship

In the detail page for an item, my app lets users tag the item from a list of previously created tags. Items and tags are each an entity in Core Data and are connected via a many-to-many relationship.
When the user selects the tag in the item detail page, I am trying to create the relationship in Core Data as follows:
_selectedTag.item=self.item; // where self.item is the item being viewed.
While there is only one item being viewed, self.item, the relationship is many to many meaning an item can have more than one tag and tags can be assigned to more than one item.
Accordingly, the above line throws a warning: Incompatible pointer types assigning to NSSet from items and when you run the app it crashes.
Can anyone suggest the proper way to set this relationship.
Thank you.
When you set up a many-to-many relationship, CoreData generates the methods you need to add the links between the two entities involved in the files it generates. For example, for an entity Track that I use I have a many-to-many relationship with the entity Contact thats named includesContact in the Track record. Based on this, CoreData generates the following method hooks in the file Track+CoreDataProperties.swift:
// MARK: Generated accessors for includesContact
extension Track {
#objc(addIncludesContactObject:)
#NSManaged public func addToIncludesContact(_ value: Contact)
#objc(removeIncludesContactObject:)
#NSManaged public func removeFromIncludesContact(_ value: Contact)
#objc(addIncludesContact:)
#NSManaged public func addToIncludesContact(_ values: NSSet)
#objc(removeIncludesContact:)
#NSManaged public func removeFromIncludesContact(_ values: NSSet)
}
It generates the reverse method hooks in the file for the Contact entity.
When I need to add a single Contact to the includesContact relationship, I use addIncludesContact with a single Contact argument. Alternatively, I use the NSSet version to add relationships for multiple Contacts.
For example, to add a single Contact, myContact, to a new Track called newRecord, this works as follows:
newRecord.addToIncludesContact(myContact)
For multiple Contacts stored in the array selectedContacts, it goes as follows:
newRecord.addToIncludesContact(selectedContacts as NSSet)
Hope that helps...

Realm creation and population sequence

I'm trying to build up a Realm of a bunch of data. This wouldn't be a problem but I've hit a wall - a gap in experience shall we say.
Creating one record is fine. However, one of the fields of that record is an array (<List>) of records from another table. Now my 2 questions are:
Does Realm support that? A list or array of Objects as one of the fields for a record... Answering no here will leed me on to an answer of my question - I will simply need to make an array of "primary keys" and query with those when I need to. If the answer is yes, proceed to question 2.
How would I go about creating those lists, bearing in mind that those tables might be created at a fraction of a second later than the current one, meaning those records don't yet exist and therefore can't be added to the list...
Example:
class baseRLMObject: Object {
// Creates an id used as the primary key. Also includes a few methods.
}
class Film: baseRLMObject {
var name: String!
var episodeId: Int!
var characters = List<Character>()
}
class Character: baseRLMObject {
var name: String!
var films = List<Film>()
}
See how all the film objects need to be created first before the character objects? Otherwise I could try add a film which does not yet exist and then it all crashes and burns :( Reason I want to try find a better way is, I'm dealing with 10 tables, a few hundred records and variable connection speeds. It would be too long to wait for each data retrieval to finish before the next one starts. AND since they are all suffering from the same problem (inter-connections), regardless of which I start with, it won't work...
Thank you :)
As discussed, for the object that haven't been created, you should create an empty object with only the primary key, then re-fetch and add value after the other network request called
For Many-to-many relationship, you can use Realm's Inverse Relationships to create linking between these objects like:
class Character: baseRLMObject {
var name: String!
var films = LinkingObjects(fromType: Film.self, property: "characters")
}
Like it's being discussed in the comments, you should be able to use Realm's inverse relationships feature to automate most of this process:
class baseRLMObject: Object {
// Creates an id used as the primary key. Also includes a few methods.
}
class Film: baseRLMObject {
var name: String!
var episodeId: Int!
var characters = List<Character>()
}
class Character: baseRLMObject {
var name: String!
let films = LinkingObjects(fromType: Film.self, property: "characters")
}
When calling character.films, a List will be returned of all of the Film objects whose characters property contains that object.
This is a lot easier and error-free than trying to maintain two separate relational lists between object types.

Will CoreData duplicate objects?

I have an NSManagedObject called "Routine," that has already been saved to my data model. It has a to-many relationship to another NSManagedObject called "Workout". I want to edit the Routine in order to add more workout relationships to it to it.
let routine = fetchedResultsController.objectAtIndexPath(indexPath) as! Routine
The ViewController where I edit the Routine in my data model contains an array of Workout objects:
var myWorkouts = [Workout]()
Some of the workouts in the "myWorkouts" array may have been already associated with Routine, whereas others may not have (a new workout). I create a relationship between each workout and the routine like this:
for workout in myWorkouts {
routine!.setValue(NSSet(objects: workout), forKey: "workout")
}
My Question: If a relationship between a Routine and a Workout has already been created, will the above for-loop create a duplicate of that workout to associate with my routine, or will it only create new relationships for previously unassociated workouts?
I hope my questions makes sense. Thanks in advance for the help!
Routine CoreDataProperties File
import Foundation
import CoreData
extension Routine {
#NSManaged var name: String?
#NSManaged var workout: Set<Workout>?
}
So, you're working with Sets, which means that they only always contain each value once. Therefore, regardless of the enclosing objects (in this case NSManagedObjects), there will only be one in there. You're good - re-stating the relationship won't change anything.
I might suggest, however, that you can do a couple of things to make your life easier:
If you haven't already, create the concrete subclasses using Xcode's built in tools, so you can directly access relationships and properties.
In the concrete subclasses +NSManagedObjectProperties file, redefine those to-many relationships from NSSets? to Set<MyClass>?. This allows you to call Swift-native functions, and works correctly, as Set is bridged from NSSet.
In the future, just call routine.workout = workout, which is much clearer than the way your code defines setting the relationship.

Why can't I use a relationship in a NSManagedObject subclass computed property? (CoreData, swift)

I'm using CoreData and I have a Book entity and a ReadingSession entity. Each Book has many ReadingSessions.
If I add this computed property to the Book class, it works:
var sessions: [ReadingSession] {
let request = NSFetchRequest(entityName: "ReadingSession")
let predicate = NSPredicate(format: "book = %#", self)
request.predicate = predicate
return try! DataController.sharedInstance.managedObjectContext.executeFetchRequest(request) as! [ReadingSession]
}
But if I add this one, it doesn't:
var sessions: [ReadingSession] {
return readingSession?.allObjects as! [ReadingSession]
}
This last example sometimes returns the correct array and sometimes just returns an empty array.
I tried the same thing with other relationships inside computed properties and the results are the same.
Why is that? Is there a reason why I shouldn't try doing this? Is my first example a valid workaround, or will it cause me problems later on? Should I give up using computed properties for this and just repeat the code when I need it?
Thanks in advance!
Daniel
Answering my own question, as Wain pointed out in the comments I should be able to use relationships inside computed properties, and my problem was actually somewhere else.
If you're interested in the details read the next paragraph, but, long story short, if you're having the same problem you should look into your relationships and make sure they're set properly as To One or To Many. Also check if you're setting all your properties in the right places and only when necessary.
I edited my question to remove lots of unnecessary details and make it more readable, but in the end the problem was that I had a User entity with a selectedBook property which was set when a user selected a row. I had set it up as a To Many relationship, but a user can have only one selectedBook at a time, so it should have been a To One relationship there. Also when I created a book I set user.selectedBook to it, but the selectedBook property should only be set when a user selected a book from a row. So I was setting and trying to access some of my relationships at all the right times. I tried to access a user.selectedBook before a user had even selected a row, for instance, and then it obviously returned nil, which messed up many other computed properties. Now I fixed all that and I'm able to access all my relationships from within computed properties without any issues.

Resources