web-dev-qa-db-ja.com

Swiftおよびinit(windowNibName)でのNSWindowControllerのサブクラス化

Swiftで新しいドキュメントベースのCocoaプロジェクトを開始しようとしています。また、NSWindowControllerのサブクラスを作成したいと思います(ドキュメントベースのアプリに関するAppleのガイドで推奨されています)。ObjCでは、 initWithWindowNibName:メッセージを送信するNSWindowControllerサブクラス。それに応じて実装され、スーパークラスメソッドを呼び出します。

Swift init(windowNibName)は、便利なイニシャライザとしてのみ使用できます。NSWindowControllerの指定されたイニシャライザはinit(window)であり、これは明らかにウィンドウに渡したいです。

サブクラスからsuper.init(windowNibName)を呼び出すことはできません。これは指定された初期化子ではないため、convenience init(windowNibName)を呼び出す必要があるself.init(window)を実装する必要があることは明らかです。しかし、私が持っているのが私のnibファイルだけの場合、そのイニシャライザに送信するためにnibファイルのウィンドウにどのようにアクセスしますか?

31
Martin

NSWindowControllerinit()init(window)init(coder))の3つの指定された初期化子をすべてオーバーライドするか、いずれもオーバーライドしない必要があります。サブクラスは自動的に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")

これは、言語ガイドの「初期化/自動初期化子継承」でカバーされています。

ただし、スーパークラス初期化子は、特定の条件が満たされた場合に自動的に継承されます。実際には、これは多くの一般的なシナリオでイニシャライザオーバーライドを記述する必要がないことを意味し、安全であればいつでも最小限の労力でスーパークラスイニシャライザを継承できます。

サブクラスで導入するすべての新しいプロパティにデフォルト値を提供すると仮定すると、次の2つのルールが適用されます。

ルール1サブクラスが指定された初期化子を定義しない場合、サブクラスは指定されたすべてのスーパークラスの初期化子を自動的に継承します。

ルール2サブクラスが、スーパークラスで指定されたすべてのイニシャライザの実装を提供する場合(ルール1に従って継承するか、定義の一部としてカスタム実装を提供することにより)、その後、自動的にすべてを継承しますスーパークラスの便利なイニシャライザ。

15
hamstergene

Initメソッドをオーバーライドする代わりに、windowNibNameプロパティをオーバーライドして、ハードコードされた文字列を返すことができます。これにより、基本的なVanilla initメソッドを呼び出してウィンドウコントローラーを作成できます。

class WindowController: NSWindowController {

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

let windowController = WindowController()

Nibの名前はウィンドウコントローラークラス内に完全にカプセル化され、外部に公開されるべきではない実装の詳細であるため、let windowController = WindowController(windowNibName: "NameOfNib")を呼び出すよりもこれを好みます(そしてWindowController()を呼び出すのは簡単です) 。

Initメソッドにパラメーターを追加する場合は、以下を実行します。

  • カスタムのinitメソッドでsuper.init(window: nil)を呼び出します。これにより、NSWindowControllerwindowNibNameプロパティを使用して初期化されます。
  • 必要な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)
15
ospr

便利なイニシャライザを呼び出し、カスタム変数を変更してから新しいオブジェクトを返すクラスメソッドを用意するだけで、これを回避できました。

したがって、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
    }
}
5
MaddTheSane

@hamstergeneの答えにおける天才のストロークは、NSResponderから継承されたinit()もオーバーライドすることです。これで、新しいイニシャライザを導入し、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ファイルを指示する必要がなくなりました。代わりに、最初にサブクラスを動機付けたもの(たとえば、ドキュメント階層に参加するなど)に特化できます...

1
milos