Swift - Capture keydown from NSViewController
I was trying to find an answer for swift 3, here is what worked for me:
Swift 3
import Cocoa
// We subclass an NSView
class MainView: NSView {
// Allow view to receive keypress (remove the purr sound)
override var acceptsFirstResponder : Bool {
return true
}
// Override the NSView keydown func to read keycode of pressed key
override func keyDown(with theEvent: NSEvent) {
Swift.print(theEvent.keyCode)
}
}
I manage to get it work from subclass of NSWindowController
class MyWindowController: NSWindowController {
override func keyDown(theEvent: NSEvent) {
print("keyCode is \(theEvent.keyCode)")
}
}
UPDATE:
import Cocoa
protocol WindowControllerDelegate {
func keyDown(aEvent: NSEvent)
}
class WindowController: NSWindowController {
var delegate: WindowControllerDelegate?
override func windowDidLoad() {
super.windowDidLoad()
delegate = window?.contentViewController as! ViewController
}
override func keyDown(theEvent: NSEvent) {
delegate?.keyDown(theEvent)
}
}
and ViewController:
class ViewController: NSViewController, WindowControllerDelegate {
@IBOutlet weak var textField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
override func keyDown(theEvent: NSEvent) {
textField.stringValue = "key = " + (theEvent.charactersIgnoringModifiers
?? "")
textField.stringValue += "\ncharacter = " + (theEvent.characters ?? "")
textField.stringValue += "\nmodifier = " + theEvent.modifierFlags.rawValue.description
}
}
Xcode 8.2.1 • Swift 3.0.2
import Cocoa
class ViewController: NSViewController {
@IBOutlet var textField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
NSEvent.addLocalMonitorForEvents(matching: .flagsChanged) {
self.flagsChanged(with: $0)
return $0
}
NSEvent.addLocalMonitorForEvents(matching: .keyDown) {
self.keyDown(with: $0)
return $0
}
}
override func keyDown(with event: NSEvent) {
switch event.modifierFlags.intersection(.deviceIndependentFlagsMask) {
case [.command] where event.characters == "l",
[.command, .shift] where event.characters == "l":
print("command-l or command-shift-l")
default:
break
}
textField.stringValue = "key = " + (event.charactersIgnoringModifiers
?? "")
textField.stringValue += "\ncharacter = " + (event.characters ?? "")
}
override func flagsChanged(with event: NSEvent) {
switch event.modifierFlags.intersection(.deviceIndependentFlagsMask) {
case [.shift]:
print("shift key is pressed")
case [.control]:
print("control key is pressed")
case [.option] :
print("option key is pressed")
case [.command]:
print("Command key is pressed")
case [.control, .shift]:
print("control-shift keys are pressed")
case [.option, .shift]:
print("option-shift keys are pressed")
case [.command, .shift]:
print("command-shift keys are pressed")
case [.control, .option]:
print("control-option keys are pressed")
case [.control, .command]:
print("control-command keys are pressed")
case [.option, .command]:
print("option-command keys are pressed")
case [.shift, .control, .option]:
print("shift-control-option keys are pressed")
case [.shift, .control, .command]:
print("shift-control-command keys are pressed")
case [.control, .option, .command]:
print("control-option-command keys are pressed")
case [.shift, .command, .option]:
print("shift-command-option keys are pressed")
case [.shift, .control, .option, .command]:
print("shift-control-option-command keys are pressed")
default:
print("no modifier keys are pressed")
}
}
}
To get rid of the purr sound when pressing the character keys you need to subclass your view, override the method performKeyEquivalent and return true.
import Cocoa
class View: NSView {
override func performKeyEquivalent(with event: NSEvent) -> Bool {
return true
}
}
Sample Project
Swift4
Just found a solution for the very same problem, Swift4. The idea behind that: if the pressed key was handled by a custom logic, the handler shall return nil, otherwise the (unhandled) event...
class MyViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// ...
NSEvent.addLocalMonitorForEvents(matching: .keyDown) {
if self.myKeyDown(with: $0) {
return nil
} else {
return $0
}
}
}
func myKeyDown(with event: NSEvent) -> Bool {
// handle keyDown only if current window has focus, i.e. is keyWindow
guard let locWindow = self.view.window,
NSApplication.shared.keyWindow === locWindow else { return false }
switch Int( event.keyCode) {
case kVK_Escape:
// do what you want to do at "Escape"
return true
default:
return false
}
}
}
And here we are: no Purr / Funk sound when key is pressed...
[Update] Added check of keyWindow. Without this, keyDown() is fired even if another view/window contains the first responder...