Core Data Object Injection (with Dependency) Storyboard - ios

I'm trying to use segues for passing core data MOC and Entities to other View Controllers.
So I'm using the prepareForSegue method and doing something like this
SecondViewController *svc = (SecondViewController *)[segue destinationViewController];
//passing the current managed object context to the other view controller
svc.managedObjectContext = managedObjectContext
I then want the pass the currentEntity to the same view controller
//rootEntity is -- TheManagedObject * rootEntity in the second view controller
svc.rootEntity = currentEntity
I'm not sure if the above svc.rootEntity is the right way to do it but it feels like the right way to do it to inject the currentEntity in the next view controller.
In the Second View Controller I want to insert a new object for the entity based on the rootEntity injection above.
I know I would normally create a new Managed Object by doing this:
NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:#"TheNewObject" in ManagedObjectContext:managedObjectContext //MOC injected from the First View Controller
My issue is that I want to do the above newObject but I want it to be dependent (relationship) to the first passed entity (the above rootEntity).
I've come close but I keep getting unassociated NewObjects (should be one to many)
The next step would the be to repeat the above and insert another level in the next view controller based on the the NewObject in the second view Controller.
I've read Zarra's book and a few others but they all use init methods that don't seem to work with segues.
Thanks,

you are doing everything right. Once you are in your new view controller, just proceed as you would originally when inserting new entities and relationships. After all, you are referring to the same managed object context.
So for example, if you want to insert an new entity which is a relationship you would do something like this:
NSManagedObject *newObject = [NSEntityDescription
insertNewObjectForEntityForName:#"SubEntity"
inManagedObjectContext:managedObjectContext];
newObject.rootEntity = self.rootEntity;
The newObject of kind "SubEntity" is now associated to the rootObject.

I don't think storyboards or segues have anything to do with your problem.
Where is the code where you are establishing the relationship? You should be able to simply go
[self.rootEntity addNewObjectsObject:newObject];
or, simpler to do from the many end of the relationship:
newObject.rootEntity = self.rootEntity;
(note I have assumed the relationship names here).
As a bonus, you don't need to pass in the managed object context. You can obtain this from the rootEntity object- all managed objects have a reference to their context - rootEntity.managedObjectContext

Related

How do I create a NSManagedObjectContext that's a subset of another NSManagedObjectContext?

I'm trying to use an NSFetchedResultsController(FRC) for my UITableViewController because I like the functionality that comes with a FRC as opposed to trying to manually manage my UITableView
My problem is that I need to have a filter on my table view, and my filter cannot easily be accomplished without using 'predicateWithBlock' which doesn't work with my FRC See Related Post
So what I want to do at this point is to create a context specifically for my FRC, then only add the objects I want to display in my FRC to this separate context. This way my FRC doesn't need to filter anything, it only needs to pull objects directly out of this temporary context.
So my question is how can I create this 'child' context? Is this even a good idea?
I've tried to create an empty context then insert objects from the main context, but it did not look trivial trying to manually copy NSManagedObjects from one context to another. I'm wondering if there's a better way to create a subset that I'm missing?
NSMangedObjectContext * myFRCContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
NSArray * managedFoobarObjectsToInsert = [myRealContext getFoobarObjects];
for(id managedObject in managedObjectsToInsert) {
if([managedObject passesFilterConditions])
//...now what?
}
So my question is how can I create this 'child' context?
Well, a child context is a thing. Just create a context and set its parent to your original managed object context.
https://developer.apple.com/documentation/coredata/nsmanagedobjectcontext/1506529-parent
There is discussion at the top of the NSManagedObjectContext documentation about how a child context relates to its parent.

Core data relationship: When fetching, do you automatically get second entity through relationship?

I want to retrieve an attribute from an entity to which the entity I am querying is connected via a one to one relationship. I am fetching an object first in a table view using NSFetchedResultsController and then, if you select a row of the table, through prepareforsegue.
In the prepareforsegue in the tableviewcontroller, the item in question is identified through its index path and the destination view controller gets the object with the following code:
Items *item = [self.fetchedResultsController objectAtIndexPath:indexPath];
IDDetailVC *destViewController = segue.destinationViewController;
destViewController.item=item;
Then in the detail or destination view controller, I have access to all the attribute of the item object through self.item.attribute1 etc.
My question is, what is right way to create relationship with second entity, say Addresses, to get an attribute from it? I have tried item.address and it throws an error while address.address is null.
I confirmed that you do not get this automatically but need to set it upon creating the item with newItem.otherInfo = otherInfo.name.

How to delete a temporary object on a child managed object context?

I have a CodeData model [Phone]<<--->>[Forwarding]. So the Phone object has a Forwardings set, and vice versa.
I have a list of Phones and want to add one of them to a new Forwarding.
In the ForwardingViewController I do:
// Create a new managed object context; set its parent to the fetched results controller's context.
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[managedObjectContext setParentContext:[fetchedResultsController managedObjectContext]];
self.forwarding = (ForwardingData*)[NSEntityDescription insertNewObjectForEntityForName:#"Forwarding"
So this creates a child MOC and a now temporary Forwarding.
Then I pass self->forwarding to my PhonesViewController which shows all Phones (in a table). This view controller is simply navigation-pushed.
When the user taps on one of the Phones in the table I do:
[self.forwarding addPhonesObject:phone];
The addPhonesObject is a CoreData generated accessor.
Now, when the user is back at the ForwardingViewController and taps the Cancel button (because he decides he does not want to create a new Forwarding after all), it is dismissed, which cleans up this child managedObjectContext and also self.forwarding.
After doing the above, I get a database error (Cocoa error 1550). When trying to understand the console output, my guess is that the Forwarding was indeed deleted, but that the Phone object (which of course is still there), now has a null reference to this deleted Forwarding.
My question. How should I handle this case correctly: Having a temporary object created on a child MOC, link it to another object (on the parent MOC), and then delete this temporary object again.
What is the actual error you are getting?
From your description, I am guessing that your PhonesViewController is listing phones from a different NSManagedObjectContext than the one that you created the ForwardingData entity from. This violates the relationship rule with Core Data. The rule is simple, to create a relationship between two entities they must both be from the same NSManagedObjectContext instance.
I question why you are creating a temporary NSManagedObjectContext in this situation. Since you are retaining the ForwardingData entity and you know when you are being cancelled, it seems cleaner to just delete the temporary entity when cancel is pressed instead of standing up another NSManagedObjectContext.
Update
If you need to use the child (per your comment), then you should change your PhonesViewController to accept a NSManagedObjectContext via dependency injection. Then you can send it the same NSManagedObjectContext instance as the one you used to create the new entity. With that change everything will work as you expect it to.

Editing/Adding a Core Data entity with the same view?

First, let me explain what I'm trying to accomplish. I've got a master-detail application with a MasterViewController and a EditViewController. The MasterViewController contains an Add button and a table listing Core Data entities. When the user taps a table row or the Add button, the Edit view should pop up. I'm confused about how I should handle editing and adding differently.
Here's how I'm currently doing it: my app uses Storyboards, so I have editEntity and addEntity segues from Master to Edit. Both segues pass an entity to the EditViewController, but editEntity finds an existing entity based on the row tapped whereas addEntity creates a new one. Both segues set the isNew transient property on the entity.
The EditViewController doesn't know anything about Core Data--it simply edits the entity it's given. It in turn has done and cancel unwind actions. MasterViewController looks at the isNew property when considering cancel--if the entity is new, it deletes it, and if it already exists, it simply doesn't apply the changes.
This works, but it has a couple problems. Firstly, it seems a tad messy to add extra properties to the entity. Secondly, if the user closes the app on the Edit view while editing a new entity, that entity won't be deleted, which is certainly unexpected. Most of all, this seems like a problem that Core Data itself must have a solution to--I just don't know how. Thanks a bunch!
The simplest improvement would be to replace the isNew flag on the entity description with a flag on your edit view controller. The edit VC might not know anything about Core Data, but it's OK to let it know if the object it's editing is new or pre-existing. Set the flag there, and have the master VC check the value before deciding how to proceed. Don't put this in your entity description, it's not data you need to keep around.
What I've done in this situation is create the new instance but don't insert it in the managed object context yet. Something like
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Entity" inManagedObjectContext:[self managedObjectContext]];
NSManagedObject *myObj = [[NSManagedObject alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:nil];
Passing nil for the second argument when creating the instance gives you an instance that hasn't been inserted yet. Pass that to the edit view controller.
If the user taps on the save button, you can insert it later with something like:
if ([myObj managedObjectContext] == nil) {
[[self managedObjectContext] insertObject:myObj];
}
Since the object hasn't been inserted, it has no managed object context, so checking that property tells you whether to insert it. Don't use the isInserted property here, it won't do what you need. Save changes in either case.
If the user taps "cancel", just don't insert it. The object gets deallocated just like any other object, and never makes it to the persistent store. Since you never inserted it, you don't need to bother deleting it.

MagicalRecords: Can't save simple relationship

I'm new to MagicalRecord so this may be a simple thing but I've looked everywhere for an answer and no one seems to be having my issue.
I have a simple Core Data structure with one Parent leading to many Child object with the relationship children one way, and parent the other.
As expected this works...
Parent *parent = [Parent MR_createEntity];
parent.name = #"John";
Child *child1 = [Child MR_createEntity];
child.name = #"Paul";
parent.children = [NSSet setWithObject:child];
// or child.parent = parent;
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveToPersistentStoreWithCompletion:nil];
I generate a tableview of all the children belonging to one parent, and again this works fine. Clicking on a row takes you to a controller that displays the selected child's details. This controller has two properties passed to it from the table: the Parent the table belongs to, and the Child that was clicked.
I then have a save button for any changes made to the details. It triggers the following:
self.child.parent = self.parent;
/* all the other details of the child */
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveToPersistentStoreWithCompletion:nil];
[self.navigationController popViewControllerAnimated:YES];
but I always get this error
CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. The left hand side for an ALL or ANY operator must be either an NSArray or an NSSet. with userInfo (null)
2013-05-09 17:30:17.312 NoteMD[37109:c07] __70-[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:]_block_invoke21(0x815f750) Unable to perform save: The left hand side for an ALL or ANY operator must be either an NSArray or an NSSet.
I get the same error whatever I try (such as [self.parent addChildObject:self.child] or using existingObjectWithID to get local versions of self.parent and self.child).
I guess I'm misunderstanding how to set relationships in MagicalRecord but I'm running out of things to try....
EDIT: This is the core data setup. I tried to simplify it above but basically Patient is Parent, and Check is Child (with patient = parent and checks = children).
Also, in app delegate it is init via
[MagicalRecord setupCoreDataStack];
Also, if I NSLog self.child, or self.parent in the detail view, both look like the right objects.
You are most likely saving the wrong context. ContextForCurrentThread: is now deprecated as it doesn't always return the proper context. When you save the wrong context it can look like your saves aren't working. I suggest being explicit with the context you're using in your object creation by using the inContext: parameter and them save that context with the mr_save method
As often happens when trying to make a problem manageable it looks like it's nothing to do with the code I posted. When moving from the parent table view to the parent detail view and then the child table view I used the following predicate to get the list of child
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY parent.id like %#", self.parent.id];
It was the ANY that was causing the issue.

Resources