Search code examples
cocoansdocumentnsdocumentcontroller

Document-based app doesn't restore documents with non-file URLs


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:

  • –init
  • –initWithContentsOfURL:ofType:error:
  • –initForURL:withContentsOfURL:ofType:error:
  • –initWithType:error:

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?


Solution

  • 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.