在Swift中继承NSWindowController并使用init(windowNibName)函数

33
我试图在 Swift 中启动一个基于 Cocoa 的新文档项目,并想要创建 NSWindowController 的子类(在 Apple 的文档中关于基于文档的应用程序中推荐这样做)。 在 ObjC 中,您将创建一个 NSWindowController 子类的实例,发送 initWithWindowNibName: 消息,然后按照实现方式来实现,调用超类方法。
在 Swift 中,init(windowNibName) 仅作为方便的初始化器提供,而 NSWindowController 的指定初始化器是 init(window),显然希望我传递窗口。
我不能从我的子类调用 super.init(windowNibName),因为它不是指定的初始化器,所以我显然必须实现 convenience init(windowNibName),进而需要调用 self.init(window)。但如果我只有我的 nib 文件,该如何访问该文件的窗口并将其发送到该初始化器呢?
5个回答

22

不需要覆盖任何init方法,您只需覆盖windowNibName属性并返回一个硬编码的字符串即可。这样您就可以调用基本的原始init方法来创建窗口控制器。

class WindowController: NSWindowController {

    override var windowNibName: String! {
        return "NameOfNib"
    }
}

let windowController = WindowController()

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

如果你想向 init 方法添加其他参数,请按照以下步骤进行:

  • 在自定义的 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)

实际上,苹果公司本身表示NIB的名称是一项实现细节,不应该是必需的,但是他们描述如何子类化NSWindowController以实现这一点的方式只适用于Obj-C,并且在Swift中完全失败。请自行查看:https://developer.apple.com/library/archive/documentation/DataManagement/Conceptual/DocBasedAppProgrammingGuideForOSX/KeyObjects/KeyObjects.html 查找An NSWindowController Subclass Manages Nib Files - Mecki
1
现在,Apple建议您在Swift中子类化NSWindowController时覆盖windowNibName。请查看此处的“Subclassing NSWindowController”部分。https://developer.apple.com/documentation/appkit/nswindowcontroller - macshome

17

你需要覆盖NSWindowController的三个指定初始化方法之一(init(), 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继承它们或作为其定义的一部分提供自定义实现),则自动继承其超类的所有便利初始化程序。


2
请确保在“Identity Inspector”中将nib的“File Owner”设置为使用自定义的NSWindowController类,将窗口插座拖到窗口上,然后将自定义的NSWindowController实例存储为类的属性,否则它会被垃圾回收。 - Bjorn
有趣的是,这与苹果关于如何在 Obj-C 中子类化 NSWindowsController 的说法完全不兼容。请参阅 https://developer.apple.com/library/archive/documentation/DataManagement/Conceptual/DocBasedAppProgrammingGuideForOSX/KeyObjects/KeyObjects.html 并转到 An NSWindowController Subclass Manages Nib Files - Mecki

5

我能够通过创建一个类方法,调用方便的初始化函数,修改自定义变量,然后返回新的对象来解决这个问题。

所以,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
    }
}

1
很棒的想法,这绝对看起来是最好的选择。尽管我讨厌它最终会使非可选属性(var info)变成可选的。 - logancautrell
我找到了这篇文章,(至少在这种情况下)覆盖windowNibName可以解决这个问题。http://dev.eltima.com/post/91454912064/nswindowcontroller-subclass-in-swift-project - logancautrell

1
@hamstergene的答案中的创意之处在于覆盖了继承自NSResponderinit()方法。现在可以引入一个新的初始化器并将其委托给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 中进行初始化真是一团糟。 - Andy Hin

-1

对 hamstergene 答案的更新。

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

Add your custom class to window controller

//
//  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())

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接