Core Data To Many Peer Relationship - objective-c

I'm writing a Core Data based Cocoa app for recipes. I have an Ingredient entity, and want to create a ingredientSubstitutes To Many relationship to other Ingredients, but I'm getting errors either when setting the relationship or when saving the store that I can't figure out. Here's the Entity description:
Ingredient
Attributes:
ingredientName type:String
Relationships:
ingredientSubstitutes destination:Ingredient inverse:ingredientSubstitutes
In my Nib I have 3 array controllers:
All ingredients AC
Available substitutes AC
Selected ingredient substitutes AC
I have 3 table views that each display the contents of these array controllers. I then have a button to add one ingredient as a subsitute for another, that is bound as follows
Button bindings
Target: All Ingredients AC.selection
Selector Name: addIngredientSubstitutesObject:
Argument: Available Substitutes AC.seletion
With this setup, as soon as I click the add button, the app throws unrecognized selector sent to instance exception: "-[_NSControllerObjectProxy entity]: unrecognized selector sent to instance", as if Ingredient doesn't recognize addIngredientSubstitutesObject. I added a proxy method to make sure that's the selector that's not recognized, and that is indeed the problem.
After trying a bunch of things and getting no where, as an experiment, I then changed the model, so that ingredientSubstitutes has no inverse:
Ingredient
Attributes:
ingredientName type:String
Relationships:
ingredientSubstitutes destination:Ingredient inverse:*none*
When I run this the add is successful, and all the tables update accordingly, but on save, I get a different unrecognized selector and the app throws an exception:
-[_NSControllerObjectProxy _isKindOfEntity:]: unrecognized selector sent to instance
Any suggestions as to what might be going on? Am I taking the wrong approach?

Figured it out, this thread helped tip me off: Core Data Programmatically Adding a to-many Relationship
Basically, binding the target to the Array Controller's selection meant that the target object was an Array Controller's Proxy object for an Ingredient, not an actual Ingredient, which apparently does not respond to the To Many accessors that Ingredient does. I solved this by instead implementing a method in the app delegate that gets the actual objects and can use the To Many accessors:
- (void)addSubstituteForSelectedIngredient:(OFIngredient *)ingredient
{
OFIngredient *selectedIngredient = [[self.allIngredientsArrayController selectedObjects] objectAtIndex:0];
OFIngredient *selectedSubstitute = [[self.availableSubsArrayController selectedObjects] objectAtIndex:0];
[selectedIngredient addIngredientSubsObject:selectedSubstitute];
}
Note that NSArrayController's - (id) selection method (which I tried before, as mentioned in the original description) returns a proxy object, so you must use (NSArray *)selectedObjects!

Related

Core Data, populating mandatory relationship from a newly created NSManagedObject

This should be easy, how difficult can it be.
I have a document based core data application with a very simple data model.
I have a "node" entity with a parent/children relationship to itself controlled by a NSTreeController, and viewed through an NSOutlineView. The "node" also has a non optional (to one) relationship to another entity type "nodeProperties" which is managed by a NSArrayController. I have NSManagedObject sublasses for both of the entities. My document class has outlets bound to both the tree controller and array controller instances.
My problem is how to ensure that, when a new "node" is created by a user interface action in the outline view, its relation to a suitable (pre-existing) nodeProperties object is populated.
Approaches I have tried / considered:
Let the tree controller create the "node" (from its add:, addChild: actions) and populate the relationship to a nodeProperties object in the "node" subclass awakeFromInsert method. The trouble is I cannot find a means of accessing any nodeProperties object from within the "node"s awakeFromInsert. The "appropriate" nodeProperties object is available from a method in the document class, but accessing the document object from the node awakeFromInsert method seems to break the principles of MVC, and I have read that the shared document object is not always safe in a drag and drop operation (which in my case also creates a new node object)
Write add: and addChild: action methods in the document class and invoke these from the end user actions instead of the tree controller (My drag and drop support is also in the document class). Then from within these methods invoke the add: and addChild: methods in the tree controller, then set the nodeProperties relationship on the newly created node. The trouble is I don't know how to ask the tree controller to give me a reference to the newly created node? I have tried using the selectedObjects method to get the parent, and then comparing the parents children before and after the add to get the new node. But the children content does not change at this time - perhaps it is a delayed update?
As a variant of 2, don't use the tree controller add:/addChild: methods at all, but instead create the node entity object in the document add:/addChild: methods using the tree controllers selectedOjects to get the parent. I don't really like this since it seems like doing something behind the tree controllers back, and I would have to setContent: each time I created root objects.
I have considered the possibility of observing the creation of the newly created node, but I don't know what to observe to achieve that.
Someone must have done something like this before - but I trawled to no avail. All help, advice, guidance would be very welcome.
OK so after much trawling and experimentation the answer was a variant of 3. The document creates the new node, populating its mandatory relationship, in add and addChild action methods, and then inserts the node into the tree controller using the method
NSTreeController insertObject:atArrangedObjectIndexPath:
For those interested, this is my addChild method in the document class. It has a few specifics from my data model
- (IBAction)addChildAction:(id)sender
{
NSArray *indexPaths = [nodeTreeController selectionIndexPaths];
NSArray *selectedObjects = [nodeTreeController selectedObjects];
for (NSUInteger i = 0; i < [indexPaths count]; i++)
{
QVXpandNode *parentNode = [selectedObjects objectAtIndex:i];
if ((parentNode) && ([parentNode.isMaster boolValue])) // can only add nodes under the master node
{
QVXpandNode *createdNode = [self createPopulatedNode];
// Dont belelieve below is safe when >1 selected,
// since adding a new node will result in the tree paths changing?
// Hmmm but I do want to support multiple selection addition??
[nodeTreeController
insertObject:createdNode
atArrangedObjectIndexPath:[[indexPaths
objectAtIndex:i] indexPathByAddingIndex:[parentNode.children count]]];
}
}
}
You will see that I am unsure whether I will put the second and later children at the right path if >1 rows were selected before calling the action.
The addSibling method is slightly more complicated by the need to calculate the last index path value, but is otherwise similar. I can reproduce it if anyone wants to see it, but the key to populating a mandatory relationship in a new tree node is to do it in the document class and then tell the tree controller precisely where in the tree you want to insert it.

Add NSArray to CLBeaconRegion via setValue forKey causes Error

Is it possible to add an Array to a CLBeaconRegion via a setValue forKey?
I've tried to add it:
[regionAdvert setValue:haveArray forKey:#"advertArray"];
But I receive just the following error:
> Terminating app due to uncaught exception 'NSUnknownKeyException',
> reason: '[<CLBeaconRegion 0x15379600> setValue:forUndefinedKey:]: this
> class is not key value coding-compliant for the key advertArray.'
setValue:forKey: is a KVC method which is basically available on every class - but that doesn't mean you can just pass anything to it. The key you use needs to be an existing property (well, really a method as that is what gets called, or a handled non-existent key). Anyway, the point is that you should know the key exists before trying to set it.
Technically you can use objc_setAssociatedObject to associated arbitrary objects with other objects but in this case I would encourage you to instead use the major and minor properties to check the purpose of the region before deciding how to display your alert.
No, you can't do this. If you want to associate other data with a CLBeaconRegion, then you can use other data structures like NSDictionary. Since each CLBeaconRegion is constructed with a unique "identifier" string, you can use this identifier as a key into a NSDictionary to store your other arbitrary data objects.
Also, you aren't supposed to change a CLBeaconRegion once constructed. The fields are immutable. This isn't really a big deal -- there are only three fields. Just create a new CLBeaconRegion, copying any of the identifiers from the old CLBeaconRegion that you wish to retain.

Cannot perform operation because childrenKeyPath is nil

Why do I get this crash error when I try to insert a child into my NSTreeController?
NSTreeController *tree = [NSTreeController new];
NSTreeNode *node1 = [NSTreeNode treeNodeWithRepresentedObject:#{ #"type": #"shirt" }];
// The below method causes mysterious crash
[tree insertObject:node1 atArrangedObjectIndexPath:[NSIndexPath indexPathWithIndex:0]];
Xcode says:
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '[object class: NSMutableDictionary] Cannot perform operation because childrenKeyPath is nil'
*
Why is causing this error? What is childrenKeyPath and why do I need it (I'm not using interface builder)?
I've made several mistakes here. You should not insert instances of type NSTreeNode into an NSTreeController with insertObject. You use insertObject to insert a model object, at which point an NSTreeNode will be automatically created for it.
Also, in your model object, you need to have a property or key which is set as an NSMutableArray. Then, before you insert any model objects, you set the childrenKeyPath property of NSTreeController to equal the name of that property or key.
This is because NSTreeController and NSTreeNode are not designed to hold data or child objects themselves, but act as a simple but helpful "map" for the models you've created and retained.
I hope this information helps others because the rest of StackOverflow was oddly silent about it...

How can I create a Core Data relationship from one entity to another, existing entity?

During the creation of a Core Data entity (Event), I am creating a relationship to another entity (Team). This relationship is many-to-one from Team to Events (one team, many events) and has an inverse relationship from Event to Team.
Team<----->>Event.
The delete rule for both relationships is set to 'Nullify'.
The below block of code works successfully on first population when a new Team is created during the creation of each Event. However, if I then remove an Event and attempt to re-add it, the existing Team is retrieved but the code fails when attempting to add the Team object to the Event in the final line of the example. The error is as follows: -[__NSCFDictionary managedObjectContext]: unrecognized selector sent to instance 0x699ed60
What is the correct way to create a relationship between the Event object to the Team object that already exists?
Team *currentTeam = self.team;
Team *newTeam = (Team *)[self loadTeamForNid:[NSNumber numberWithInteger: [teamNid integerValue]]];
// If the nid of the referenced team has changed,
if (![[[currentTeam nid] stringValue] isEqualToString:teamNid]) {
currentTeam = nil;
currentTeam = newTeam;
}
// If an event has not been set by this point, it does not exist in the CD store, and we need to create it.
if (currentTeam == nil) {
currentTeam = (Team *)[NSEntityDescription insertNewObjectForEntityForName:#"Team" inManagedObjectContext:[delegate managedObjectContext]];
[currentTeam populateTeamWithNode:[node nodeGet:teamNid]];
}
// TODO: This breaks on reload of an object
// self.team = currentTeam;
[self setValue:currentTeam forKey:#"team"];
Conceptually, you aren't mistaken: you set the event's "team" property to an instance of NSManagedObject that represents the appropriate team.
This message:
-[__NSCFDictionary managedObjectContext]: unrecognized selector sent to instance 0x699ed60
Means that some line of code is handling an instance of NSDictionary where it expects (I assume) an instance of NSManagedObject. When it tries to query the object's managedObjectContext, an exception is thrown, because an NSDictionary doesn't implement a method for that selector.
The first thing to do is put a breakpoint on that last line and see if currentTeam is actually an NSDictionary in disguise. (This seems unlikely, given the code above an exception would have been hit earlier.) If not, you'll have to hunt around for related properties that might be involved in this code path.
Note that Core Data supports a fetch request style where it returns NSDictionary instances instead of NSManagedObjects; if you are using this anywhere in your code, you might be accidentally passing the result along to another method that doesn't expect it.

NSTableView sort problem(with KVC)

I had a weird problem on NSTableView sort.
I created a simple app with a NSTableView which
has 4 columns. Then I used KVC to bind it to a
Array controller.
Then I added some data to the NSMutableArray.
Bulid&&Run the app and I can see the data inside
the table and by clicking the header the data can
be sorted correctlly.
Everything works fine up till now.
Then I tried to add the "caseInsensitiveCompare" to
each columns. So I opened the IB, set the sort key and
selector ("caseInsensitiveCompare:") to each columns.
Then Bulid&&Run the app, but when I click the header to
sort, I got the error message:
-[NSCFNumber caseInsensitiveCompare:]: unrecognized selector sent to instance 0x1006254f0
-[NSCFNumber caseInsensitiveCompare:]: unrecognized selector sent to instance 0x1006254f0
......
Then I tried to delete all the content in the sort key and
selector of each columns. And the app became OK again.
What seems to be the problem? I am really confused...
ps:
If I use compare: instead of caseInsensitiveCompare:, everything works fine again...
One of the values (such as a property or key) of your objects in your NSMutableArray is of class NSNumber. Since this class deals with numbers, it doesn't respond to the caseInsensitiveCompare: selector. This selector is meaningful to NSString.
The column on your table view that is displaying number values should keep using compare: for sorting the values.