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,
acceptsFirstResponder
is called twice. - When hitting any key,
performKeyEquivalent(with:)
is called twice, too. Inspectig the intermediate variableretVal
above 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...