NSPersistentDocument and Swift

It’s been a while since I’ve worked with NSPersistentDocument. My last dabbling with it was back in the days of Objective-C. Ahh Objective-C. Oh, and I was using xibs instead of storyboards.

With xibs, the user interface file had an object called “File’s Owner” which would be a handy reference right directly to my subclass of NSPersistentDocument. That would give me easy access to the all important managed object context (moc).

With Objective-C, I could conveniently override the - (id)initWithType:(NSString *)typeName error:(NSError **)outError method in NSPersistentDocument to populate a newly created document with whatever entities I wished.

Easy. Or at least it was easy after I had figured all that out some 5 years ago.

Now it is easy again but I had to burn up a couple of days figuring it all out all over again.

Stuff I learned over the weekend…

Where to start… let’s start with populating your subclass of NSPersistentDocument when it is instanced for a new document.

You can’t override a convenience initializer in Swift. Well… actually you can but then you lose all the goodness that comes from NSPersistentDocument version of the convenience initializer. Inconvenient? You betcha. That was the way I could populate a new document in the old days. But we love Swift so we will find a workaround.

I looked all over the Internet for this workaround and didn’t find anything satisfying. But what I did do is quickly read this “Discussion” in the documentation for managedObjectContext:

If a managed object context for the document does not exist, one is created automatically. If you want to customize the creation of the persistence stack, reimplement this property in your custom subclass and use your implementation to create the appropriate objects.

Quickly reading this I got “blah blah create blah objects.” So I thought this might be the hook to populate a new document with entities. It seemed to work. I could add stuff to the moc. The storyboard would show what was created in the interface. Seemed to be working.

But not saving. I was adding stuff to the moc before it was ready. The place I’m looking at now to populate the document is at the end of the override func makeWindowControllers() method. I don’t know if this is the best place to do it but a) it works, b) all the goodies that NSPersistentDocument needs to create are in place at this time. It might be better at the start of the method… but stuff is working as it is so… I’ll poke the bear later.

My populate method needs to check the state of the document and add things that are missing. In the old days, I’d know what the state was and it wouldn’t be required when opening an existing document.

Next… getting that all important managedObjectContext in the storyboard. Or “The Magic of the RepresentedObject.”

I only really like magic in my D&D games or in the movies and definitely not in my code. But the representedObject in a NSViewController is magic. If you don’t cast this spell just right, the magic won’t work.

I asked and answered the question on stackoverflow so you can see the details there.

Things that don’t seem to work…

I saw somewhere that someone wrote in makeWindowsController: windowController.contentViewController!.representedObject = windowController.document Don’t do it. Even though it seems to be right (didSet in the controller’s representedObject shows the right object being setted), the magic didn’t work for me.

Don’t try to be tidy and create a method to skirt around the Model Key Path of self.representedObject.managedObjectContext in your storyboard. Your tidy method will look tidy and right but the magic won’t work. At least it didn’t for me. And I tried many many different variations.

Anyhow… looking at it all now, it is all pretty easy. It just took the weekend to tease it all out.

In summary, In Swift, NSPersistentDocument doesn’t have a handy spot to hook in populating a new document like Objective-C had. I’ll have to test every document created to see if it is populated or not and go from there. And with storyboards, representedObject is your new temperamental friend. Treat it nicely, pass it nicely around to other view controllers, and it will give you much moc.