I have a NSDocument
based OS X app which instead of creating new documents with a blank page shows a panel to the user to chose a template from, like for example Apple's Pages.app does.
I made this by manually creating a new NSDocument
instance when the user starts a new document and immediately setting a property on it to reflect which template the user has chosen for it:
MyNSDocumentSubclass *newDoc = [sharedDocumentController makeUntitledDocumentOfType:fileType error:&err];
[newDoc setTemplate:templateChosenByUser]; // autosave doesn't care about this line
[sharedDocumentController addDocument:newDoc];
[newDoc makeWindowControllers];
[newDoc showWindows];
This works fine until the user decides to restart the computer or close the application without saving: When App Kit's autosave functionality tries to restore the former state of my application on its next launch, it fails to do so for the user template property because it never noticed that MyNSDocumentSubclass
' model state has changed and didn't autosave the whole document in the first place. It instead, probably for performance optimization reasons, just creates a fresh instance of MyNSDocumentSubclass
and the user's template choice is lost.
To fix this, I added
[newDoc updateChangeCount:NSChangeDone];
to reflect the model chance introduced by the user's choice of template. Now, autosave kicks in correctly and does save the document prior to computer or app shutdowns. The only drawback (and my problem I'm asking for help with here) is, that this is the wrong way to do: NSChangeDone
is meant to reflect a chance to the document model initiated by the user. Thus, it causes an "edited" already being shown in the new document's window and displays a save panel to the user when closing the window. The user's template choice, though, is not really a change which should count as an "edit" as it is tightly coupled to the creation of a new document. Luckily, the OS X release notes talk about exactly this problem and offer a solution:
Some applications use
-updateChangeCount:
to causeNSDocument
to autosave changes that don't originate directly from the user. For example, when importing a non-native document type, some applications create a new document with the imported contents and call -updateChangeCount:
to ensure that the document gets autosaved with those contents. Many applications useNSChangeDone
for this purpose. However, since the user did not explicitly cause this change, it is undesirable to turn this document into a draft. Applications should be careful to use the correctNSDocumentChangeType
—in this case,NSChangeReadOtherContents
—to prevent the conversion into a draft. UsingNSChangeDiscardable
will also prevent the creation of a draft.
Unluckily, [newDoc updateChangeCount:NSChangeReadOtherContents]
doesn't work at all. It surely does suppress the "edited" in the window's title, but at the same time it prevents autosave from doing its job when terminating the app: the document doesn't get autosaved and looses the value of its template property upon the next launch.
So, what can I do? I want my freshly created NSDocument
subclass to autosave the user's template choice. At the same time, it shouldn't show as already being edited to the user when in fact they just created it.
The only thing I can think of is trying to save the template property by using the NSWindowRestoration
protocol, but that feels plainly wrong. Also, window restoration kicks in way too late in the document reopening process.
Another idea I had is to create several different NSDocument
subclasses (reflected by different fileTypes with a single UTI) for each template an user can chose instead of using a property on my NSDocument
subclass - but this feels wrong none the less.
Otherwise, I am lost. Thanks for your help.
I don’t think there will be a clean way to do what you want, since fundamentally you’re trying to store something the user set in the autosave file and NOT have the NSDocument notice the file is “dirty,” when by Apple’s definition it is, in fact, dirty.
BUT, I think you can accomplish what you want, if you’re willing to get a bit dirty. One idea is simply to do something like:
[newDoc updateChangeCount:NSChangeDone];
[newDoc autosaveWithImplicitCancellability:NO completionHandler:^(NSError *errorOrNil){
[newDoc updateChangeCount:NSChangeCleared];
}];
I know, I know, this isn’t clean.