Can't use storyboard custom instantiated window controller
Asked Answered
P

2

10

I am getting what feels like a bug when trying to custom instantiate a window controller from a storyboard. I am using NSStoryboard.instantiateController(identifier:creator:), which is a new function as of MacOS 10.15. The block of code in question is:

let mainWC = storyboard.instantiateController(identifier: "id") { aDecoder in  
    MainWindowController(coder: aDecoder)  
} 

I have SUCCESSFULLY used basically this exact code for custom instantiating the main view controller, and just assigning that view to a new window and a new window controller. That works fine. I can also instantiate the window controller the old fashioned way without custom initialization with instantiateController(identifier:). But when I try the above code for custom instantiation of the window controller I end up with the following error:

Assertion failure in -[NSClassSwapper _createControllerForCreator:coder:]... Custom instantiated controller must call -[super initWithCoder:]

Note that both my custom view controller class (which works) and my custom window controller class MainWindowController (which doesn't work) have implemented the trivial initializer:

required init?(coder: NSCoder) {  
    super.init(coder: coder)  
}

I know that this functionality is new as of OS 10.15, but the documentation says it should work for window controllers AND view controllers, and the error message does not make any sense to me.

Phosphorescent answered 21/2, 2020 at 17:23 Comment(4)
Do you use NSStoryboard.instantiateController(identifier:creator:), NSStoryboard.instantiateController(identifier:) or NSStoryboard.instantiateInitialController(creator:)?Stool
NSStoryboard.instantiateController(identifier:creator:)Phosphorescent
The link is to the documentation of NSStoryboard.instantiateInitialController(creator:).Stool
It looks like a bug to me.Stool
I
6

I hit the same problem, I thought about it a bit and here is how I worked around it.

First, why do I need this for ? I wanted to inject some dependencies to my view controller hierarchy before it's built from the Storyboard. I guess that's what the API is intended to. But then, would that method be working, how would I pass the injection information down the view controller hierarchy ?

So, as the method is working without bug for view controllers, I decided to inject the information directly at the root view controller.

So, I have in my storyboard :

  • A window controller scene named "my-window-controller", which window just points to an empty view controller.
  • A view controller scene named "root-view-controller", where all the view hierarchy is described.

And wherever I want to create that view controller, I just do :

func instanciateWindowController(storyboard: NSStoryboard) -> NSWindowController {

    //  Load the (empty) window controller scene
    let wcSceneIdentifier   = NSStoryboard.SceneIdentifier("my-window-controller")
    let windowController    = storyboard.instantiateController(withIdentifier: wcSceneIdentifier)
            as! NSWindowController

    //  Load the root view controller using the creator trick to inject dependencies
    let vcSceneIdentifier   = NSStoryboard.SceneIdentifier("root-view-controller")
    let viewController      = storyboard.instantiateController(identifier: vcSceneIdentifier,
                                                               creator: { coder in
        return MyOwnViewController.init(coder: coder,
                                        text:   "Victoire !") // just pass here your injection info
    })

    //  Associate the window controller and the root view controller
    windowController.contentViewController  = viewController

    return windowController
}

with

class MyOwnViewController: MSViewController {
    init?(coder:   NSCoder,
          text:    String) { // receive here the injection information
        print(text) // use the injection information here
        super.init(coder: coder)
    }

    // Not used, but required
    required init?(coder:   NSCoder) {
        super.init(coder: coder)
    }
}
Impartial answered 18/4, 2020 at 9:32 Comment(2)
Dang, I wish I had learned this a month ago. I ended up just manually re-creating my main window, window controller, and toolbar directly in code and tying that all to the root view controller. But your answer allows the use of storyboard. It looks like the key here is is to link the window to a dummy view controller; I had tried this with my window controller linked to my main view controller in the storyboard, which caused all kinds of havoc. I consider this an acceptable answer.Phosphorescent
If you're interested in dependency injection and storyboards, you may have a look at this : #25787039 In my answer, I propose what I consider to be "the right way" to do dependency injection with storyboards. Most people advice to use prepareForSegue, but I think this is not a good solution.Huelva
A
0

This is filed as Feedback #FB7626059, if you’d like to pile on (I hit the issue too).

Animalcule answered 14/3, 2020 at 10:3 Comment(3)
Thanks, I also submitted this under FB7595235. How do you view feedback that other people have submitted?Phosphorescent
You cannot, sadly. But supposedly if you include other FB #s in the reports it helps.Animalcule
You can cross-post your bug reports to openradar.appspot.com Apple doesn't use it, but you can cross post their responses manually. It's crappy, but it makes your tickets more discoverable, searchable, etc.Aslant

© 2022 - 2024 — McMap. All rights reserved.