Search code examples
cocoanstextfieldnsdocumentnsapplicationnsapplication-delegate

Make one last change to NSDocument before app terminates/window closes?


I have a basic NSDocument-based app. I need to make one last change to the document when:

  1. The user closes the document's window
  2. The user terminates the app

Why?

The document window contains an NSTextField. Usually text entered into this text field is committed to the document's model after the user presses Enter (via textDidEndEditing(_:)).

Let's assume the user typed some text, but does not press Enter. Instead he presses Cmd-W or Cmd-Q to close the document window or terminate the app altogether.

textDidEndEditing is not called so I check if the text field contains changes and try to update the document myself.

❌ Here is where it gets tricky. The following results in a deadlock on NSDocument.performActivityWithSynchonousWaiting:

override func viewWillDisappear() {
    super.viewWillDisappear()

    undoManager?.disableUndoRegistration()
    textField.commitEditing() // Updates the model

    document.updateChangeCount(.changeDone)
}

I managed to work around the deadlock by not hooking into viewWillDisappear, but into NSDocument.canClose(withDelegate delegate: Any, shouldClose shouldCloseSelector: Selector?, contextInfo: UnsafeMutableRawPointer?).

✅ This code causes the changes to be saved when the user closes the document window:

override func canClose(withDelegate delegate: Any, shouldClose shouldCloseSelector: Selector?, contextInfo: UnsafeMutableRawPointer?) {

    undoManager?.disableUndoRegistration()
    textField.commitEditing() // Updates the model

    updateChangeCount(.changeDone)

    super.canClose(withDelegate: delegate, shouldClose: shouldCloseSelector, contextInfo: contextInfo)
}

.

❓Unfortunately, the above is not called when the user terminates the app. I tried updating my document in applicationWillTerminate() -- did not work. I also tried overriding applicationShouldTerminate() and delaying termination for 3 seconds. I can see the document's window being marked as "edited", but changes are not saved.

How can I make a last change to NSDocument right before the app terminates and have it saved automatically?


Solution

  • For reference: I don't think it is possible to make a last change to NSDocument while the app is being terminated. The document architecture is simply not designed that way (see https://www.mail-archive.com/[email protected]/msg107739.html)

    At the time the app is terminating, the document's windows have been closed and window controllers released.

    I eventually solved this problem by changing the design of my app to record changes to the document as the user types. I use a new instance of UndoManager and manually call NSDocument.updateChangeCount(.changeDone) to register my changes out of the undo/redo chain. When the user finally commits changes by pressing Enter, I use the document's undo manager to register the change.