I am trying to create an application for OS X using Swift and Cocoa. I want the application to respond to key events without the focus being on/in a text field. I created a new Cocoa project with storyboards in Xcode, and have modified the ViewController.swift class to the following:
import Cocoa
class ViewController: NSViewController {
override func mouseDown(theEvent: NSEvent) {
println( "Mouse Clicked" )
}
override func keyDown(theEvent: NSEvent) {
println( "Key Pressed" )
}
override func viewDidLoad() {
super.viewDidLoad()
}
override var representedObject: AnyObject? {
didSet {
}
}
}
When I run the program, I get console output when I click on the window, but I don't get any console output when I press any keys.
So my questions are: Why does my program respond to mouse events but not key events? What extra steps do I need to do for key events to work as the mouse events do?
It appears that my view controller is in the responder chain since it intercepts the mouse events, so that doesn't seem to be the problem. I have found other answers on here on SE saying I need to subclass the NSView or NSWindow class. Is it possible to get key events without doing that?
Thanks in advance.
EDIT: In addition to the accepted answer, Swift - Capture keydown from NSViewController is a great, clear solution.
The difference you observed originates from the fact that Cocoa handles mouse events and keyboard events somewhat differently. In early stage of the event dispatching process, all events are sent to the
NSWindow
object by system. For mouse events,NSWindow
forwards them to the view on which user clicked; for keyboard events,NSWindow
will forward them to the first responder of current key window.The initial first responder of an
NSWindow
is the window itself. When user clicks on anNSView
,NSWindow
will try to make that view the first responder by sending anacceptsFirstResponder
message. If it returnsYES
, the clicked view will become first responder.The "gotcha," however, is that
acceptsFirstResponder
returnsNO
by default. As a plainNSView
won't become the first responder, it won't receive keyboard events. So the easiest solution is to subclass yourNSView
, overrideacceptsFirstResponder
to returnYES
.