9

I want to initialize a window controller object from a nib file, quite easy right? But I simply can't get it to work.

According to my previous experience in ObjC, I've written down the following code:

init()  {
    super.init(windowNibName: "SplitWindowController")
}

And in the app delegate file, I simply init and displays the window:

var myWindowController: MyWindowController = MyWindowController()
myWindowController.showWindow(self)
myWindowController.window.makeKeyAndOrderFront(nil)

But the compiler gives me this error: Must call a designated initializer of the superclass 'NSWindowController'. And according to the Swift version of NSWindowController definition, there are only 3 designated initializers, namely init(), init(window), init(coder). I don't know what to do next. Shall I build a NSCoder from a nib file, which I don't know how to do?

jscs
  • 63,694
  • 13
  • 151
  • 195
Void Main
  • 2,241
  • 3
  • 27
  • 36
  • @George see here to answer your question: [Subclassing NSWindowController][1] [1]: http://stackoverflow.com/questions/24220638/subclassing-nswindowcontroller-in-swift-and-initwindownibname – mqueue Aug 29 '14 at 21:22

2 Answers2

6

You were almost there. You can indeed override init() as a convenience initialiser in a manner that is equivalent to the Obj-C code you got used to:

import Cocoa

class MyWindowController: NSWindowController {

    override convenience init() {
        self.init(windowNibName: "<xib name>")
    }
}

Note that you are calling init(windowNibName:) on self, because init() being a convenience initialiser, you still inherit all the initialisers from the superclass. From documentation:

Rule 1: A designated initializer must call a designated initializer from its immediate superclass.

Rule 2: A convenience initializer must call another initializer from the same class.

Rule 3: A convenience initializer must ultimately call a designated initializer.

Also, as @weichsel mentioned above, make sure you set the class of the File's Owner to your subclass of NSWindowController (in the example above, that would be MyWindowController) and then connect its window outlet with the window itself.

That being said, I'm not sure why is compiler asking for the override keyword to be added. Though NSWindowController is a subclass of NSResponder, which defines an init(), the following code compiles without issue even though it implements an equivalent inheritance hierarchy:

class A {
    init() { }
}

class B: A {
    init(Int) {
        super.init()
    }
    convenience init(String) {
        self.init(5)
    }
}

class C: B {
    convenience init() {
        self.init("5")
    }
}
Milos
  • 2,728
  • 22
  • 24
  • 1
    I get an "Initializer does not override a designated initializer from its superclass" error in Xcode 7.3. Removing the `override` keyword gets rid of the error. – Nicolas Miari Aug 12 '16 at 03:56
  • 1
    @NicolasMiari, obviously, that particular bug has been addressed by Apple since this post was written. Thanks for making a note of that! What also changed is that we now have to explicitly mark the unnamed parameters (instead of `init(Int)`, we now write `init(_: Int)`. – Milos Aug 12 '16 at 20:21
4

NSWindowController has 2 designated initializers:

init(window: NSWindow!)
init(coder: NSCoder!)

When creating a subclass, you should invoke the designated initializer of its superclass. Recent versions of Xcode enforce this. Either via built-in language mechanism (Swift) or via NS_DESIGNATED_INITIALIZER macro (Objective-C).

Swift additionally requires that you call the superclasses designated initializer when you override a convenience initializer.
From the "Initialization: Designated Initializers and Convenience Initializers" section of Swift Programming Guide:

If the initializer you are overriding is a convenience initializer, your override must call another designated initializer from its own subclass, as per the rules described above in Initializer Chaining.

In your case, you should probably override init(window: NSWindow!) and call super's counterpart from there.

Thomas Zoechling
  • 34,177
  • 3
  • 81
  • 112
  • Thanks for your explanation, I wonder how can I construct a NSWindow object from nib file? – Void Main Jul 04 '14 at 15:48
  • I mean the window object should be constructed from the nib file, right? And how can I construct the window from nib file? – Void Main Jul 04 '14 at 15:56
  • If you have a dedicated xib for your window controller, you can set your subclass as "Custom Class" in the File Owner's Identity Inspector. Note that when the window controller is created from the xib, you have to override initWithCoder instead of initWithWindow if you want to do something specific in the initializer. – Thomas Zoechling Jul 04 '14 at 17:11
  • @weichsel Could you please elaborate? I'd like to subclass NSWindowController so that the subclass knows what Nib to load when initialized. In Obc-C, this is done by calling "initWithNibName:" from "init". How can this be done in Swift? – George Aug 16 '14 at 12:09
  • 1
    I had this same problem. The best I've found is the 2nd answer here (use a class factory method instead): http://stackoverflow.com/questions/24220638/subclassing-nswindowcontroller-in-swift-and-initwindownibname – logancautrell Sep 14 '14 at 17:50
  • @logancautrell's comment is key. Check out that answer. – Bjorn Jan 03 '15 at 02:35