Thomas Tempelmann
Thomas Tempelmann

Reputation: 12043

How to prevent automatic State Restoration if the app was launched to open specific documents?

I have implemented State Restoration in my AppKit (Cocoa) app. That works.

However: If the user opens the app by double clicking a document file saved by the app, then I want the app not to restore the previous windows but only open the document that was opened in Finder.

The problem is that the state restoration appears to happen before the handlers for opening document files are invoked, so even if I had a way to suppress the state restoration, I have no knowledge of the pending openURL calls at that point, yet.

I understand that I could check the launch arguments, but that seems to be a rather dirty way. I'd think that I should be able to query the stored file paths/URLs from the app object somehow, but I can't find anything.

Or can I change the order of restoring documents vs. opening documents? I've so far found out that both happens deep inside [NSDocument restoreDocumentWindowWithIdentifier:state:completionHandler:], inside the completionHandler function. I could not find an overridable function below that where I'd have more control over this, though.

I implement State Restauration by overriding restoreStateWithCoder: and encodeRestorableStateWithCoder: in my NSDocument subclass.

Upvotes: 6

Views: 757

Answers (3)

Willeke
Willeke

Reputation: 15589

Undocumented (= unsupported) feature: Restoring all documents when the app is launched by opening a document can be disabled. In applicationWillFinishLaunching do

[NSUserDefaults.standardUserDefaults setBool:YES forKey:@"NSDiscardWindowsOnDocumentOpen"];

Upvotes: 2

uliwitness
uliwitness

Reputation: 8793

Can you maybe delay state restoration? Queue up all the data you get for restoring state, return an "everything fine" value, and then wait for 'oapp' Apple Event to restore it (or just clear the queue if an 'odoc' arrives).

Upvotes: -1

Thomas Tempelmann
Thomas Tempelmann

Reputation: 12043

My current solution is to intercept the odoc AppleEvent, and if that's invoked, set a flag that causes [NSApp restoreWindowWithIdentifier:] to return NO.

Here's the code inside AppDelegate.m:

static AEEventHandlerUPP openDocHandler = NULL;
static SRefCon openDocRefcon;

static pascal short HandleOpenDocMessage (const AEDesc *theAppleEvent, AEDesc *reply, void* handlerRefcon)
{
    // intercepts the "odoc" Event to check if we're about to open any files, and then continues with the original handler 
    NSAppleEventDescriptor *event = [NSAppleEventDescriptor.alloc initWithAEDescNoCopy:theAppleEvent];
    NSURL *url = [NSURL URLWithString:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]];
    if (url) {
        NSString *docType = [NSDocumentController.sharedDocumentController typeForContentsOfURL:url error:nil];
        if ([docType isEqualToString:@"..."]) { // I can check for particular types here in case I can open several
            [(Application*)NSApplication.sharedApplication setSuppressRestorationOfSearchWindows:YES];
        }
    }
    return openDocHandler (theAppleEvent, reply, openDocRefcon);
}

- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
    AEGetEventHandler(kCoreEventClass, kAEOpen, &openDocHandler, &openDocRefcon, false);
    AEInstallEventHandler(kCoreEventClass, kAEOpen, HandleOpenDocMessage, (__bridge SRefCon)(self), false);
    ...

Inside Application.h:

@property (assign) BOOL suppressRestorationOfSearchWindows;

Inside Application.m:

- (BOOL)restoreWindowWithIdentifier:(NSString *)identifier state:(NSCoder *)state completionHandler:(void (^)(NSWindow *, NSError *))completionHandler // override
{
    if ([identifier isEqualToString:@"MyWindowType"] && self.suppressRestorationOfSearchWindows) {
        // prevent window restoration on startup if the user launched this app by opening a file of my particular type
        return NO;
    } else {
        // continue with regular state restoration
        return [super restoreWindowWithIdentifier:identifier state:state completionHandler:completionHandler];
    }
}

Feels like a hack, though.

Upvotes: 2

Related Questions