Handling Keyboard Input in a View Controller – Alongside SpriteKit
I’m using SpriteKit in the train game I’ve been tinkering with on and off, and recently went to add keyboard event handling to my view controller:
override func keyDown(event: NSEvent) {
interpretKeyEvents([event])
}
override func deleteBackward(sender: AnyObject?) {
println("deleteBackward")
}
To my surprise, though, whatever was first responder wasn’t passing keyDown: up the responder chain. (This is 10.10 so view controllers are in the responder chain automatically.)
First, determining what was first responder was easy enough. After showWindow: had been called:
(lldb) ex windowController.window!.firstResponder
(SKView) $R2 = 0x0000000100e1b100 {
The SKView is the first responder. I had forgotten that SKNode is a subclass of NSResponder and that nodes can participate in the responder chain.
So why wasn’t the keyDown: propagating up to the view controller? I checked the nextResponder of the SKView: it was the same as its superview, which is to be expected. The question remains: why wasn’t keyDown: propagating?
For the answer to that I turned to Hopper Disassembler, which revealed that -[SKView keyDown:] essentially sends keyDown: to its scene:
void -[SKView keyDown:](void * self, void * _cmd, void * arg2) {
rbx = self;
r14 = [arg2 retain];
if (rbx->_disableInput == 0x0) {
rdi = rbx->_scene;
if (rdi != 0x0) {
[rdi keyDown:r14];
}
}
rdi = r14;
rax = [rdi release];
return;
}
So how can I deal with this? I could subclass SKView and override keyDown: to give it a more traditional implementation (pass it on to nextResponder, for example). However, while I was in Hopper I noticed that SKScene doesn’t implement keyDown:, which means that it is relying on the default implementation of keyDown: in NSResponder. Which means that the simplest solution is to set the scene’s next responder to my view controller:
let gameScene = GameScene(size: ...)
skView.presentScene(gameScene)
assert(gameScene.nextResponder == nil)
gameScene.nextResponder = self
Now my view controller receives keyboard events: problem solved! My guess is that SpriteKit’s designers intended for the keyboard event handling to be done in an SKScene subclass – that’s how it’s done in the Adventure sample code. Unfortunately that doesn’t make sense for my app architecture, where there’s more to the UI than an SKView.