Saturday, September 8, 2012

Cocoa OSX - Using NSDocument with NSViewController and bindings

This is a quick blog about something I discovered when trying to use cocoa bindings, NSDocument, all controlled by a NSViewController.  Since it took me a while to figure this out, and I didn't see any decent solutions posted, I'll post about it.

Requirement:
-  You have a NSDocument which is your model object
-  You decide to assign it to a NSViewController via the responsibleObject property.
-  You create a binding from something like a NSTextView to the responsibleObject.title (for example), which is backed by your NSDocument.
-  You want it to work, save on close, save if someone closes the NSWindowController that happens to also exist, etc....

Problem:
-  Your binding appears to work great, you can see data, edit data, etc..
-  During the usecase of pressing command-S, closing the window, or saving WHILE the NSTextView still has focus, i.e. commitEditing has not been called anywhere, your NSDocument saves without calling commitEdit, because your binding is actually with the NSViewController, not the NSDocument (which is held inside responsibleObject of your NSViewController).

Possible Solutions, and what I think the right solution is:
1.  I found a solution, where I can call commitEditing on the NSViewController, perhaps manually from overriding save on the NSDocument.  Seems like I was fighting something here, it should work more natural.  However I saw at least 2 suggestions where people did stuff like this, but I refused to cave in early.

2.  Find a way to give up firstResponder status of the NSTextEdit during the close, save, or window exit, which causes the NSTextEdit to loose focus and commit its changes.  Also read about people doing stuff like this.

3.  What I think the right solution was:

After reading up on NSEditor and realizing that my NSViewController did know via the calls that the binding logic from NSTextView did, it called objectDidBeginEditing on NSViewController, etc...  It seemed clear that somehow I needed to let NSDocument know about this, because it also seems to implement NSEditorRegistration calls like objectDidBeginEditing.  Basically I wanted NSDocument to automatically call commitEditing because it seems to have NSEditorRegistration logic inside it already. So I ended up having my NSViewController call the NSDocuments NSEditorRegistration calls in overrides like this:

- (void)objectDidBeginEditing:(id)editor {
     [super objectDidBeginEditing:editor];
    [[self representedObject] objectDidBeginEditing:self];
    NSLog(@"ReceiptDetailViewController - objectDidBeginEditing");
}

- (void)objectDidEndEditing:(id)editor {
     [super objectDidEndEditing:editor];
     [[self representedObject] objectDidEndEditing:self];
    NSLog(@"ReceiptDetailViewController - objectDidEndEditing");
}

So, this receive the binding editing alert, records it, then downstream to the NSDocument, stored in the representedObject, registers a change with our own NSViewController as the editor.  It worked, so far great, and seems to cover all use-cases I tested so far (closing the window, save, etc).  What seems to happen is the NSDocument now knows about the in-flight edit, calls back the NSViewController, asking it to commit all edits, it knows about local edits, and automatically asks them to commit.

I'm asking for comments for sure, is there a better way?  Did I break something else that I am not yet aware of.

Thanks,
Marcus


No comments:

Post a Comment