I have an application based on NSDocument with an NSDocumentController subclass. My NSDocument works with both file URLs and URLs with a custom scheme which use a web service.
I handle much of the loading and saving using custom code, including -saveToURL:ofType:forSaveOperation:completionHandler:
. +autosavesInPlace
returns YES
.
The problem I'm having: documents with the custom URL scheme aren't restored on startup. Documents with the file URL scheme are – both regular documents saved to files, and untitled documents which are autosaved.
After leaving open server-based documents and quitting the app, no NSDocument methods appear to be called on restart. In particular, none of the four initializers is called:
The NSDocumentController method -reopenDocumentForURL:withContentsOfURL:display:completionHandler:
is not called either.
How and when are documents' restorable state encoded? How and when are they decoded?
NSDocument is responsible for encoding its restorable state in -encodeRestorableStateWithCoder:
, and NSDocumentController is responsible for decoding documents' restorable state and reopening the documents in +restoreWindowWithIdentifier:state:completionHandler:
. Refer to the helpful comments in NSDocumentRestoration.h
.
When NSDocument encodes the URL, it appears to use the bookmark methods of NSURL. The problem is that these methods only work with file-system URLs. (It's possible non-file URLs will encode, but they will not properly decode.)
To fix the problem, override the encoding of NSDocument instances which use the custom scheme, and likewise, the decoding of those documents.
NSDocument subclass:
- (void) encodeRestorableStateWithCoder:(NSCoder *) coder {
if ([self.fileURL.scheme isEqualToString:@"customscheme"])
[coder encodeObject:self.fileURL forKey:@"MyDocumentAutoreopenURL"];
else
[super encodeRestorableStateWithCoder:coder];
}
NSDocumentController subclass:
+ (void) restoreWindowWithIdentifier:(NSString *) identifier
state:(NSCoder *) state
completionHandler:(void (^)(NSWindow *, NSError *)) completionHandler {
NSURL *autoreopenURL = [state decodeObjectForKey:@"MyDocumentAutoreopenURL"];
if (autoreopenURL) {
[[self sharedDocumentController]
reopenDocumentForURL:autoreopenURL
withContentsOfURL:autoreopenURL
display:NO
completionHandler:^(NSDocument *document, BOOL documentWasAlreadyOpen, NSError *error) {
NSWindow *resultWindow = nil;
if (!documentWasAlreadyOpen) {
if (![[document windowControllers] count])
[document makeWindowControllers];
if (1 == document.windowControllers.count)
resultWindow = [[document.windowControllers objectAtIndex:0] window];
else {
for (NSWindowController *wc in document.windowControllers)
if ([wc.window.identifier isEqual:identifier]) {
resultWindow = wc.window;
break;
}
}
}
completionHandler(resultWindow, error);
}
];
} else
[super restoreWindowWithIdentifier:identifier
state:state
completionHandler:completionHandler];
}
The behavior or the completion handler follows from Apple's method comment in NSDocumentRestoration.h and should be roughly the same as super
's.
Window state encoding is enabled by two methods on NSWindow
. Calling setRestorable:
on the window marks it as one that can be saved and restored on relaunch, and then calling setRestorationClass:
lets you specify a class that will handle recreating that saved window.
By default, AppKit sets NSDocumentController
as the restoration class of windows controlled by NSDocument
objects. The actual restoration is done by calling the method +restoreWindowWithIdentifier:state:completionHandler:
, defined by the NSWindowRestoration
protocol. For documents, NSDocumentController
implements that method and recreates the NSDocument
object based on the state encoded in the NSCoder
instance passed into the method.
So, theoretically, if you were to subclass NSDocumentController
and override that method, that would give you an opportunity to restore documents saved by the state restoration mechanism. However, as far as I know, the keys used by NSDocumentController
to store state are not documented anywhere, so I don't think there would be a reliable way to restore directly from the state that NSDocumentController
stores itself.
To support this, you would probably need to encode the entire state for the document yourself, by implementing -encodeRestorableStateWithCoder:
on the NSWindow
being encoded, and/or implement the window:willEncodeRestorableState:
delegate method for the window. Both of those methods pass you an NSCoder
instance you can use to encode your state. That's where you would encode your custom-schemed URL, along with any other associated data you need to save/restore your state. You would then decode that state in the restoreWindowWithIdentifier:state:completionHandler:
method.
Since you'll have some documents with regular file URLs and some with your custom URLs, I would approach that by creating a separate class responsible for decoding the document state, and set that as the restoration class only for your documents with custom URLs, leaving NSDocumentController
to handle the documents withe file URLs for you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With