【问题标题】:Subclassing NSWindowController in Swift and init(windowNibName)在 Swift 和 init(windowNibName) 中继承 NSWindowController
【发布时间】:2014-08-04 22:01:02
【问题描述】:

我正在尝试在 Swift 中启动一个新的基于文档的 Cocoa 项目,并希望创建一个 NSWindowController 的子类(如 Apple 的基于文档的应用程序指南中所推荐的那样)。在 ObjC 中,您将创建一个 NSWindowController 子类的实例,发送 initWithWindowNibName: 消息,相应地实现该消息,调用超类方法。

在 Swift 中,init(windowNibName) 只能作为便利的初始化器,NSWindowController 的指定初始化器是 init(window),这显然是想让我传入一个窗口。

我不能从我的子类中调用super.init(windowNibName),因为它不是指定的初始化器,所以我显然必须实现convenience init(windowNibName),而这又需要调用self.init(window)。但是,如果我只有我的 nib 文件,我如何访问 nib 文件的窗口以发送到该初始化程序?

【问题讨论】:

    标签: cocoa swift initializer nswindowcontroller


    【解决方案1】:

    您可以简单地覆盖windowNibName 属性并返回硬编码字符串,而不是覆盖任何init 方法。这允许您调用基本的 vanilla init 方法来创建窗口控制器。

    class WindowController: NSWindowController {
    
        override var windowNibName: String! {
            return "NameOfNib"
        }
    }
    
    let windowController = WindowController()
    

    我更喜欢这个而不是调用let windowController = WindowController(windowNibName: "NameOfNib"),因为 nib 的名称是一个实现细节,应该完全封装在窗口控制器类中,并且永远不会暴露在外部(而且调用WindowController() 更容易)。

    如果您想向 init 方法添加其他参数,请执行以下操作:

    • 在您的自定义初始化方法中调用super.init(window: nil)。这将使NSWindowController 使用windowNibName 属性进行初始化。
    • 重写所需的 init(coder: NSCoder) 方法以配置您的对象或简单地调用 fatalError() 以禁止其使用(尽管在运行时)。
    class WindowController: NSWindowController {
    
        var test: Bool
    
        override var windowNibName: String! {
            return "NameOfNib"
        }
    
        init(test: Bool) {
            self.test = test
            super.init(window: nil) // Call this to get NSWindowController to init with the windowNibName property
        }
    
        // Override this as required per the class spec
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented. Use init()")
    
            // OR
    
            self.test = false
            super.init(coder: coder)
        }
    }
    
    let windowController = WindowController(test: true)
    

    【讨论】:

    【解决方案2】:

    您需要覆盖NSWindowControllerinit()init(window)init(coder))的所有三个指定初始化程序,或者都不覆盖它们,在这种情况下,您的子类将自动继承 init(windowNibName) 和所有其他便利初始化器,您将能够使用超类的便利初始化器来构造它:

    // this overrides none of designated initializers
    class MyWindowController: NSWindowController {
        override func windowDidLoad() {
            super.windowDidLoad()
        }
    }
    
    // this one overrides all of them
    //
    // Awkwardly enough, I see only two initializers 
    // when viewing `NSWindowController` source from Xcode, 
    // but I have to also override `init()` to make these rules apply.
    // Seems like a bug.
    class MyWindowController: NSWindowController
    {
        init()
        {
            super.init()
        }
    
        init(window: NSWindow!)
        {
            super.init(window: window)
        }
    
        init(coder: NSCoder!)
        {
            super.init(coder: coder)
        }
    
        override func windowDidLoad() {
            super.windowDidLoad()
        }
    }
    
    // this will work with either of the above
    let mwc: MyWindowController! = MyWindowController(windowNibName: "MyWindow")
    

    语言指南中的“初始化/自动初始化程序继承”涵盖了这一点:

    但是,如果满足某些条件,超类的初始值设定项会自动继承。在实践中,这意味着您不需要在许多常见场景中编写初始化程序覆盖,并且只要安全,您就可以轻松地继承您的超类初始化程序。

    假设您为在子类中引入的任何新属性提供默认值,则适用以下两条规则:

    规则 1 如果你的子类没有定义任何指定的初始化器,它会自动继承它的所有超类的指定初始化器。

    规则 2 如果您的子类提供了其所有超类指定初始化器的实现(通过按照规则 1 继承它们,或者通过提供自定义实现作为其定义的一部分),那么它会自动继承所有超类便利初始化器。

    【讨论】:

    • 一定要在身份检查器中设置nib的FileOwner使用自定义的NSWindowController类,将window outlet拖到window上,然后将自定义的NSWindowController实例作为属性存储到类上,否则会垃圾收集。
    • 有趣的是,这与 Apple 所说的如何在 Obj-C 中继承 NSWindowsController 完全不兼容。请参阅developer.apple.com/library/archive/documentation/… 并转到 NSWindowController 子类管理 Nib 文件
    【解决方案3】:

    我可以通过一个调用便利初始化程序的类方法来解决这个问题,修改自定义变量,然后返回新对象。

    因此,Objective C init 方法如下所示:

    //Class.h
    @class PPPlugInInfo;
    
    @interface PPPlugInInfoController : NSWindowController
    //...
    - (id)initWithPlugInInfo:(PPPlugInInfo *)plugInfo;
    //...
    @end
    
    //Class.m
    #include "Class.h"
    
    @interface PPPlugInInfoController ()
    @property (strong) PPPlugInInfo *info;
    @end
    
    @implementation PPPlugInInfoController
    
    - (id)initWithPlugInInfo:(PPPlugInInfo *)plugInfo;
    {
        if (self = [self initWithWindowNibName:@"PPPlugInInfoController"]) {
            self.info = plugInfo;
        }
        return self;
    }
    
    @end
    

    这就是我制作 Swift 版本的方式:

    class PPPluginInfoController: NSWindowController {
        private var info: PPPlugInInfo!
        class func windowControllerFromInfo(plugInfo: PPPlugInInfo) -> Self {
            var toRet = self(windowNibName:"PPPlugInInfoController")
            toRet.info = plugInfo
    
            return toRet
        }
    }
    

    【讨论】:

    • 好主意,这绝对是最好的选择。虽然我讨厌这最终使非可选属性(var info)成为可选的
    • 我确实找到了这篇文章,并且(至少在这种情况下)覆盖 windowNibName 解决了这个问题。 dev.eltima.com/post/91454912064/…
    【解决方案4】:

    @hamstergene 的答案中的天才之举是也覆盖了 init(),它继承自 NSResponder。现在可以引入一个新的初始化器并委托给self.init(windowNibName: NoteWindowName),一旦所有三个指定的初始化器都被覆盖,它就会被继承:

    class WindowController: NSWindowController {
    
        var note: Document! // must be optional because self is not available before delegating to designated init
    
        convenience init(note: Document) {
            self.init(windowNibName: NoteWindowName)
            self.document = document
        }
    
        override init(window: NSWindow?) {
            super.init(window: window)
        }
    
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        override init() {
            fatalError("init() has not been implemented")
        }
    }
    

    现在不再需要告诉自定义窗口控制器从哪个 nib 文件加载。相反,它可以专门用于最初激发子类的任何事物(例如,参与某些文档层次结构)...

    【讨论】:

    • 谢谢你。 swift中的初始化真是一团糟。
    【解决方案5】:

    hamstergene 答案的更新。

    这在 Xcode 版本 6.1 (6A1052d) 上运行良好

    //
    //  MainWindowController.swift
    //  VHDA Editor
    //
    //  Created by Holyfield on 20/11/14.
    //  Copyright (c) 2014 Holyfield. All rights reserved.
    //
    
    import Cocoa
    
    class MainWindowController: NSWindowController {
    
        //override func windowDidLoad() {
        //    super.windowDidLoad()
    
            // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
       // }
    
        override init()
        {
            super.init()
            println(__FILE__, __FUNCTION__)
        }
    
        override init(window: NSWindow!)
        {
            super.init(window: window)
            println(__FILE__, __FUNCTION__)
        }
    
        required init?(coder: (NSCoder!))
        {
            super.init(coder: coder)
            println(__FILE__, __FUNCTION__)
        }
    
        override func windowDidLoad() {
            super.windowDidLoad()
            println(__FILE__, __FUNCTION__)
        }
    
    }
    

    控制台输出:

    (…/MainWindowController.swift, init(coder:))
    (…/MainWindowController.swift, windowDidLoad())
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-07-07
      • 1970-01-01
      • 2014-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-17
      相关资源
      最近更新 更多