I have moved most of the core functionality of my non-document based macOS app to a custom, embedded framework.
The app code has a standard main storyboard with an initial window, and the window has a "window content" relationship/segue into a storyboard reference pointing to a storyboard inside the embedded framework. Therein lies a custom NSViewController subclass and a custom NSView subclass.
I want to group all the input event handling code inside the framework, which means implementing mouseDown(with:) on the custom NSView subclass, and --lo and behold-- it gets called when I click inside the app window. So far, so good.
Next, I implemented keyDown(with:) to similarly handle keyboard input. However, at runtime, it does not get called and instead, I hear the annoying beep (NSBeep).
I tried implementing keyDown(with:) on the view controller instead, but it's all the same.
Finally, I tried implementing the key handler on my NSWindowController subclass instead, and that does work.
So I could get around this by forwarding the event like so:
class WindowController: NSWindowController {
override func keyDown(with event: NSEvent) {
contentViewController?.view.keyDown(with: event)
}
}
, but it is very inelegant. I would prefer to not pollute the app code with input logic.
This doesn't seem to have anything to do with embedding frameworks, however. I put together a minimal project from the 'Cocoa App' template and confirmed that indeed keyDown(with:) only gets called if implemented on the window controller code, but not on the view or view controller side.
How can I get keyDown(with:) to be called on the view or view controller (not the window or window controller) in a storyboard-based app? (so I can move it from the main app to my embedded framework).
Edit: The question has been marked as duplicate. I tried the solutions pointed in answers to the other question (namely, override acceptsFirstResponder to return true). This solves the problem in my minimal demo project, but when I tried it on my full app, it still does not work (I did see that question and did try to override acceptsFirstResponder in my app before posting this question).
I will now try to modify my minimal poeject to see if I can reporduce the issue in the main app.
Edit 2: I have refactored the minimal project to:
- Send the view controller to a separate storyboard,
- Send the view controller's storyboard and represented classes (custom view, custom view controller) to a separate, embedded framework.
Now the basic setup mirrors that of my app in all that seems to matter, but still can not reproduce the issue in the minimal project. I will investiate further...
Edit 3: I haven't been able to reproduce the issue on the minimal project. On my app's custom view, I implemented:
public override var acceptsFirstResponder: Bool {
return true
}
public override func performKeyEquivalent(with event: NSEvent) -> Bool {
let retVal = super.performKeyEquivalent(with: event)
return retVal
}
- On startup,
acceptsFirstResponderis called twice. - When hitting any key,
performKeyEquivalent(with:)is called twice, too. Inspectig the intermediate variableretValabove reveals that the super class's implementation always returnsfalse. After returning from this method,NSBeep()is called andkeyDown(with:)isn't. - If instead of
super.performKeyEquivalent(with:)I force-returntrue, I can avert the call toNSBeep()(butkeyDown(with:)is still not called...)
Edit 4 (Final):
Out of desperation, I cleared the "Custom Class" field of the window controller's Identity Inspector in my app's main storyboard (to the default NSWindowController).
Suddenly, my custom view's keyDown(with:) starts getting called.
I reinstated the custom class to confirm.
It still works.
I clean the build folder and try again.
It still works.
Now I can no longer reproduce the issue even on my main app. I really don't know what to say...