Core Data: couldn't get an answer elsewhere. Maybe here?

Core Data is great to save arrays of objects “looking like” NSDictionaries. The Array Controller loads these objects, puts them into a table view, where we can edit them, add or remove objects (with default values). That’s great.

Now, imagine I want to store a single object.

My present solution is to use an NSArrayController with a single object as content. Of course it’s a strange solution, but it just need a single line of code: [theArrayController add: self]. I just have to verify if this object is not already present before creating it.

A better solution is to use an NSObjectController. But how do I know if the managed object already exists on disk? If I test if (!myEntity) the answer will always be FALSE because the entity has been created, but as a “class”. What about the instance? How can I test this? If I test for a key of an inexistent instance, I surely crash my app.

The single object is facultative. The user can create it or not. If he creates it, I want some defaults to be applied. If he uses it, I want to retrieve the stored values. He can also delete it, and re-create a fresh one.

Ideally, this job has to be done into a NSObjectController subclass.

Any link welcome.

Other sites didn’t respond as easily and quickly than here. Maybe I didn’t find the good one? The big trend is iOS development these days.

Somewhere early on you check to see if the ObjectController has any content.

For applying defaults, you can either set them in IB with the entity selected in the model editor, or you can assign your MO/Entity to a subclass (Model Editor / Configurations, then make sure it’s selected in the list of Entities and use menu Editor > Create NSModeledObject Subclass…) and generate a category on that subclass. In there you would use:

The main reason for using a category is so that your code won’t disappear if you re-save your MO/Entity as a subclass after any changes to the MO/Entity.

Looks like the way to set the controller to a fresh new default object is to:

Hello, Dave.

That’s pretty similar to what I’ve done in my code. So there is no real difference between a NSObjectController and a single-record NSArrayController.

Conceptually, of course, it IS similar. I’ve tested with [[[myArrayController arrangedObject]count] == 0], which gave acceptable result, but is ugly. It made me feel a bit paranoid about the possible existence of a second or a third record.

NSObjectController’s content. Silly me. I am going to change for your solution.

May I ask you further questions about Core Data? This site is made primarily for ApplescriptObjC programmers, I know, but I could get answers only here.

Thank you very much for this clear and complete solution!

Well, I’m still kinda muddling my through, myself (LOTS of Google searches to figure out how-to), but I’m sure at some point somebody will be mixing ASOC and Cocoa (with some CD added) that might benefit from talk here. I happened to have just dealt with something similar to your issue as I came upon your post.

In my transition from ASOC to Obj-C I learned a lot from posts here that dealt with mixing ObjC/ASOC.

OK, I have a problem.

I have an Entity called “mapController” and I want to put some defaults when it’s created. But.

if (![mapController content]){   // No map defined yet.
    [mapController add: self];   // This should create the instance.

    NSLog(@"%@",[mapController content]); // Gives NULL.

I tried:

    BOOL ok = [mapController fetchWithRequest:nil merge:NO error:nil];
    NSLog(@"%@",[mapController content]); // Gives NULL.

The content of mapController is in the Core Data “scratch pad” but I can’t access it. I have to set one of its attributes like this:

    [[mapController content] setValue:[matrix colorReference] forKey:@"mapData"];

This gives no error, the file is marked as changed, but it I test the value:

    NSLog(@"%@",[mapController content]); // Gives NULL.

When the heck it the controller’s content really HERE? Reading the docs doesn’t help me.

Now that I look at your post, you say you have an Entity called MapController… Do you mean an NSObjectController?

This might be a given, but on your MapController, do you have “Prepares Content” checked?

Oops, it’s so obvious to me I forgot to point it out. Yes, MapController is a NSObjectController, and yes again, “Prepare contents” is checked. “Lazy fetching” is of course unchecked.

I forgot also to tell you that I invoke these methods inside the windowControllerDidLoadNib document’s method, as suggested by Apple’s docs.

Ok. I figured that… Then the next step is to see if you CAN add content:

  • (BOOL)canAdd

BOOL controllerCanAdd = [mapController canAdd];
NSLog(@“controllerCanAdd = %@\n”, (controllerCanAdd ? @“YES” : @“NO”));

If it comes back with NO then you need to figure out why that is.

Answer is YES. Good news?

Should be… Now to figure out why it won’t add… Umm…

Although it should give you errors if not set, you have the Managed Object Context binding set to your MOC, right? How about the Content Object binding? That should be not used if you’re going to just run Add: on the controller. If you’re using the Content Object binding, then you need to take care of adding your new MapMO (or whatever it’s called) to the object it’s bound to. Although, I don’t know if canAdd would come back YES if you’re using it that way.

Another one to test (looking for possible clues or errors) would be to utilize, instead of Add, is to try - (id)newObject, which not only adds an object to the controller, but also returns the object (which should be the same as content: later on), and see if that remains nil.

If that still fails, make sure you can actually create one manually, using (again, if your object is called MapMO (the subclass of the Map entity)):

theMOC() comes from some convenience functions I wrote for myself (in the AppDelegate), which you might find handy:

if newMapMO is a valid MapMO, then see if you can use addObject:newMapMO to add it to the controller, I guess.

Uh-oh. I found this in the docs:

I think I see where the problem is: I am always in the same iteration. Now I understand why everything is fine when the
[[mapController content] setValue:[matrix colorReference] forKey:@“mapData”];
is sent a bit later by another object.

Rats. Curses. Darn. How to enforce the setValue to apply within the current loop?

Well then go with - (id)newObject… Not only is the content set for later on, but you will get your new content object back immediately and you can set your properties all in the same loop.

There is no need to define a subclass, NSManagedObject contains everything I want:

works fine. Thank you! Well, this one was no precisely obvious, four hours of sleep lost. And no one pays me for that :confused:

Sleep? What’s that? Heh.

If that works, then great. I usually make the subclasses so I can then add a category on it to run awakeFromInsert, awakeFromFetch, and prepareForDelete. Sometimes they don’t get used, but for others I have a creationDate and/or modifiedDate that I set on insert (self.creationDate = [NSDate date];), and some others that are maybe based on a GUI selection (if it’s not a direct relationship). Plus I tend to run an NSLog on these methods early in development so I can be sure things are flowing like I think they should (which isn’t always a match). For others I’ve set up observers on insert/fetch so I don’t have to remember to do that when I create a new MO.

Now… Back to work!!! :slight_smile:

Well, another issue. Note that I scrupulously follow Apple’s recommendations:

I don’t want all this initialization to be recorded for undo (I don’t want an “empty” file alert for saving).

So I used

before initializing and inserting Core Data objects. Then, after that:

And. of course it does not work: the file is changed anyway and claims for saving.

What’s wrong?

Of that I have no idea. I’ve done little to no work with document based apps to start with, and have only set up and worked with the undo manager so far for only one sheet that edits an MO (just runs the undo on Cancel). I can see what you’re aiming for, but have no clue on what’s going wrong.

It seems there are TWO undoManagers: one for the NSDocument and one for the context. What the heck?

What’s this thing called Core Data? Should be “Core Disease Data”. As brilliantly conceived as iCloud.

OK, I think I will reconsider the whole design. Or write a Cooking Recipes Utility.

EDIT: a few hours later, back to a reasonable code, I set my document to work without defaults and load them only on demand.