【问题标题】:NSDocumentController currentDocument returning nilNSDocumentController currentDocument 返回 nil
【发布时间】:2012-01-18 15:02:23
【问题描述】:

我正在开发我的第一个基于 Mac 文档的应用程序。

我已经继承NSDocument,重新实现方法如

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

主窗口控制器是NSWindowsController的子类,它包含两个NSViewController子类。

我面临的问题是我需要从这些视图控制器访问当前文档。我所做的就是打电话

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

首先,在启动应用程序后,会创建一个新文档。然后,创建主窗口及其视图控制器,但上面的方法返回 nil。这是我得到的日志(使用 NSLog):

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

之后,创建一个新文档并调用此方法会产生一个非零指针,但它不会指向正确的文档,而是指向第一个:

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

请注意,在创建第二个文档后,currentDocument 指向第一个文档而不是第二个文档。

知道我在这里遗漏了什么或做错了什么吗? currentDocument 何时设置为 NSDocumentController

【问题讨论】:

  • 正确语法:'[NSDocumentController sharedDocumentController]'

标签: objective-c macos cocoa nsdocument


【解决方案1】:

(我将保留之前的答案,因为它在被问及 NSView 的子类时回答了这个问题,但现在原始发帖人已经声明他正在使用 NSViewController,有不同的考虑因素)。

对于NSViewController 控制的视图,NSViewController 旨在使用representedObject 属性附加到其数据。此抽象旨在由 NSViewController 的包含控制器管理,这听起来像是您的NSWindowController

根据您想要/需要提供多少封装,您可以将文档推送到 NSViewController(如果它们对整个文档进行操作)或仅推送与特定 NSViewController 密切相关的文档中的信息。

例如,我假设有一个软件可以编辑火车的设计信息:引擎、车厢和车尾。 NSDocument 子类包含一个引擎对象、一个守车对象和 0 个或多个汽车对象。在这种情况下,您可能有 3 个NSViews,每个都有自己的NSViewController 来处理将数据从其对象移入和移出视图。

NSWindowController 处理将每个NSViewControllerrepresentedObject 设置为它所理解的对象。例如,当视图完成加载时,窗口控制器将:

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

然后,您可以使用 NSTableView 显示汽车列表,并且(当正在查看汽车时)窗口控制器可以在选择更改时使用 [carViewController setRepresentedObject: car];(或者您可以使用绑定,具体取决于您的代码的结构)。

这样,您可以充分利用 MVC 范例,因为控制器会根据需要将视图链接到模型,但文档结构只有顶层 NSWindowController 才能真正理解。

【讨论】:

    【解决方案2】:

    来自NSDocumentControllercurrentDocument 上的 Apple 文档中写道:

    如果在其应用程序未调用时调用此方法,则返回 nil 积极的。这可能在处理拖放操作期间发生, 例如,在 readSelectionFromPasteboard: 的实现中。在 在这种情况下,请改为从 NSView 发送以下消息 与文档关联的子类:

    [[[self window] windowController] document];

    这有点模糊,因为它并不能真正限定“不活跃”的含义。可能是拖放操作是唯一的触发器,但它没有说明这是否是应用程序变为非活动状态的唯一触发器。

    也许 Apple 建议的替代方案对您有用。

    【讨论】:

    • 谢谢,但我已经尝试过了,结果仍然为零。我也不确定“不活跃”对 Apple 意味着什么,但就我而言,它发生在启动应用程序之后。没有拖放,没什么特别的。
    【解决方案3】:

    这是一个老话题,但仍然会让人头疼。应用程序的每个窗口控制器都必须设置其document 属性,并且其canBecomeKey getter 必须返回true,以便应用程序知道它必须根据前窗口设置当前文档。

    管理起来可能很烦人,特别是在使用只有一种类型的文档和一些辅助窗口(检查器、首选项等)的应用程序时。 将这些窗口放在前面会使当前文档为零,以及其他我不太了解的原因。该文档在该主题上没有那么有用(这就是堆栈溢出问题的重点)。

    就个人而言,在大多数情况下,我会通过将 AppDelegate 设置为 NSWindowDelegate 来自己管理此问题。

    然后我制作方便的访问器来获取当前窗口、文档、数据...

    @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
            }
        }
    }
    

    在 Document 类中创建窗口控制器时设置委托。

        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
        }
    

    这是迄今为止我发现的最简单的方法,可以随时设置一致的当前文档。

    【讨论】:

      【解决方案4】:

      您不能从 NSWindowController 子类(或 -[[self window] document] 在您的 NSViewController 子类中调用 -[self document] 的任何原因?这通常是在 Cocoa 中基于文档的应用程序中执行此操作的方式。

      基本上当NSDocument(子类)被创建时,它会创建所有的NSWindowControllers并将它们附加到文档中。

      更重要的是,[[NSDocumentController sharedController] currentDocument] 不会返回正确的信息,如果您打开了 2 个文档并且突然需要绘制两个文档的内容。相反,NSWindowController 应该控制到其窗口中视图的信息流,以便您可以同时管理前台和后台更改(例如,如果您的应用程序中所有窗口的后备存储都需要,则可能会发生这种情况同时刷新)。

      【讨论】:

      • 谢谢。从 NSViewController 子类中,无法通过调用 [[self window] document] 访问文档,因为 NSViewController 具有 view 属性但没有 window 属性。使用来自 NSView 的[[[self window] windowController] document] 和来自 NSViewController 的[[[[self view] window] windowController] document](如@roger 所指)确实可以工作并返回正确的文档。重要的是要提到它们在窗口/视图初始化后确实可以工作,这对我来说很好并且可以理解。
      • 抱歉,我错过了您使用 NSViewControllers 的事实。在使用 NSViewControllers 时,我添加了另一个更符合 MVC 范例的答案。希望这个更有用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-02-18
      • 2019-06-20
      • 2017-12-16
      • 2014-09-27
      • 2017-06-11
      • 2014-12-29
      • 2019-03-14
      相关资源
      最近更新 更多