NSDocumentController currentDocument returning nil
Asked Answered
T

4

11

I'm working on my first Mac document-based application.

I have subclassed NSDocument, reimplementing methods such as

- (BOOL)readFromURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError;
- (BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError;
- (void)makeWindowControllers;

The main window controller is a subclass of NSWindowsController, that contains two NSViewController subclasses.

The problem I'm facing is that I need to have access to the current document from these view controllers. What I do is calling

MyDocument *myDocument = [[NSDocumentController sharedController] currentDocument];

At first, right after starting the application, a new document is created. Then, the main window and its view controllers are created, but the method above returns nil. Here's the log (using NSLog) I get:

Just created this new document: <MyDocument: 0x10040ff10>
I'm in a view controller and current document is (null)

After that, creating a new document and calling this method results in a non-nil pointer, but it doesn't point at the right document, but to the first one:

Just created this new document: <MyDocument: 0x100437e10>
I'm in a view controller and current document is <MyDocument: 0x10040ff10>

Notice that after the second document creation, currentDocument points to the first document and not to the second one.

Any idea of what I'm missing or doing wrong here? When is currentDocument set for NSDocumentController?

Theology answered 18/1, 2012 at 15:2 Comment(1)
Proper syntax: '[NSDocumentController sharedDocumentController]'Switzer
K
6

(I'm leaving the previous answer in place because it answers the question when asked for a subclass of NSView, but now that the original poster has stated he was using NSViewController, there are different considerations).

In the case of an NSViewController-controlled view, the NSViewController is intended to be attached to its data using the representedObject property. This abstraction is intended to be managed by the NSViewController's containing controller, which sounds like is your NSWindowController.

Depending on how much encapsulation you want to/need to provide, you can either push the document to the NSViewControllers (if they operate on the whole document) or push just the information from the document that is germane to the particular NSViewController.

For example, I'll assume a piece of software that edits design information about a train: the engine, the cars, and the caboose. The NSDocument subclass contains a single engine object, a single caboose object, and 0 or more car objects. In this case, you might have 3 NSViews, each with their own NSViewController to handle moving data in and out of the views from their objects.

The NSWindowController handles setting each NSViewController's representedObject to the object it understands. For example, when the views finish loading, the window controller will then:

    [engineViewController setRepresentedObject: engine];
    [cabooseViewController setRepresentedObject: caboose];

Then, you can use an NSTableView to show the list of cars, and (when a car is being viewed), the window controller could then use [carViewController setRepresentedObject: car]; when the selection is changed (or you could use bindings, depending on how your code is structured).

This way, you take the best advantage of the MVC paradigm, as the controllers link the views to the models as necessary, but the document structure is only really understood by the top level NSWindowController.

Kun answered 27/1, 2012 at 13:16 Comment(0)
M
3

From the Apple documentation on NSDocumentController currentDocument it says:

This method returns nil if it is called when its application is not active. This can occur during processing of a drag-and-drop operation, for example, in an implementation of readSelectionFromPasteboard:. In such a case, send the following message instead from an NSView subclass associated with the document:

[[[self window] windowController] document];

This is slightly vague as it doesn't really qualify what "not active" means. It could be that a drag 'n drop operation is the only trigger but it doesn't state whether that's the only trigger for an app to become not active.

Maybe the suggested alternative by Apple is of use to you.

Melitamelitopol answered 18/1, 2012 at 23:13 Comment(1)
Thanks, but I already tried that and I'm still getting nil as result. I was also unsure of what "not active" meant for Apple, but in my case it happens right after starting the app. No drag-and-drop, nothing special.Theology
B
1

This is an old topic, but can still give headaches. Each window controller of the app must have it's document property set and its canBecomeKey getter must return true so the app knows it must set the current document according to the front window.

This can be annoying to manage, specially when working on an app with only one type of document and some auxiliary windows ( inspectors, preferences, .. ). Bringing these windows to the front makes the current document nil, among other reasons I don't understand very well. The documentation is not as helpful as it could be on the subject (That's the point of this question on stack overflow).

Personally , what I do in most cases is I manage this myself by making the AppDelegate a NSWindowDelegate.

Then I make convenient accessors to get current window, doc, data...

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    static var shared: AppDelegate { return NSApplication.shared.delegate as! AppDelegate }
        
    weak var mainWindow: NSWindow?
    
    var activeDocument: Document? {
        mainWindow?.windowController?.document as? Document
    }

    var activeData: MyDocumentData? {
        mainWindow?.representedObject as? MyDocumentData
    }
    
    var mainViewController: DocumentViewController? {
        mainWindow?.contentViewController as? DocumentViewController
    }
}

extension AppDelegate: NSWindowDelegate {

    func windowDidBecomeKey(_ notification: Notification) {
        let window = notification.object as? NSWindow
        if window != mainWindow {
            mainWindow = window
        }
    }
    
    func windowWillClose(_ notification: Notification) {
        let window = notification.object as? NSWindow
        if window == mainWindow {
            mainWindow = nil
        }
    }
}

The delegate is set when creating the window controller in Document class.

    override func makeWindowControllers() {
        let storyboard = NSStoryboard(name: NSStoryboard.Name("Document"), bundle: nil)
        let windowController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller")) as! NSWindowController
        self.addWindowController(windowController)
        windowController.contentViewController?.representedObject = self.myDocumentData
        windowController.window?.delegate = AppDelegate.shared
    }

That's the simplest method I found so far to have a coherent current document set at any time.

Bond answered 8/2, 2021 at 9:11 Comment(0)
K
0

Any reason you can't call -[self document] from your NSWindowController subclass (or -[[self window] document] in your NSViewController subclass? That's normally how this is done in a document-based app in Cocoa.

Basically when the NSDocument (subclass) is created, it then creates all of the NSWindowControllers and attaches them to the document.

More importantly, [[NSDocumentController sharedController] currentDocument] won't return the right information if you have 2 documents open and suddenly need to draw the contents of both. Instead, the NSWindowController is supposed to control the flow of information to the the views in its window so that you can manage foreground and background changes at the same time (such as might happen if the backing store for all windows in your app needed to be refreshed at the same time).

Kun answered 26/1, 2012 at 2:39 Comment(2)
Thanks. From the NSViewController subclass it's not possible to access to the document by calling [[self window] document] as NSViewController has a view property but not a window property. Using [[[self window] windowController] document] from an NSView and [[[[self view] window] windowController] document] (as pointed by @roger) from an NSViewController do work and return the right document. It's important to mention thay they do work once the windows/views have been initialised, which is fine and understandable to me.Theology
Sorry, I missed the fact you were using NSViewControllers. I've added another answer that goes more to the MVC paradigm when using NSViewControllers. Hope this one is more useful.Kun

© 2022 - 2024 — McMap. All rights reserved.