33

I have a NSView subclass and I would like it to react when the user presses the ⇧ Shift key. However, -[NSView keyDown:] (which I currently override) isn't called when modifier keys alone are pressed.

How can I be notified when the Shift key has been pressed?

zneak
  • 134,922
  • 42
  • 253
  • 328

7 Answers7

43

From the Cocoa event handling guide:

The flagsChanged: method can be useful for detecting the pressing of modifier keys without any other key being pressed simultaneously. For example, if the user presses the Option key by itself, your responder object can detect this in its implementation of flagsChanged:.

More details can be found here.

The documentation for NSResponder also states the following:

flagsChanged:

Informs the receiver that the user has pressed or released a modifier key (Shift, Control, and so on).

-- (void)flagsChanged:(NSEvent *)theEvent

Benjamin Gale
  • 12,977
  • 6
  • 62
  • 100
17

Here is an example, modified slightly from Matt Gemmell's ModKeyTest sample app. Create a basic Cocoa app with one button and hook up the button to an IBAction like this. Then try out your desired combination of keys. The docs are a bit fuzzy, but Matt's example is very clear and presents all you need to leverage this further from the docs.

- (IBAction)myAction:(id)sender {
NSUInteger flags = [[NSApp currentEvent] modifierFlags];
if ((flags & NSCommandKeyMask) && (flags & NSAlternateKeyMask) && (flags & NSControlKeyMask)) {
    NSBeginInformationalAlertSheet(@"Modifier keys Command Option Control detected", nil, nil, nil, [NSApp mainWindow], self, nil, nil, nil,
                                   @"You sneaky thing!");
}

if ((flags & NSCommandKeyMask) && (flags & NSShiftKeyMask)) {
    NSBeginInformationalAlertSheet(@"Modifier keys Command Shift detected", nil, nil, nil, [NSApp mainWindow], self, nil, nil, nil,
                                   @"You sneaky thing!");
}

if ((flags & NSAlphaShiftKeyMask)) {
    NSBeginInformationalAlertSheet(@"Modifier keys Caps Lock detected", nil, nil, nil, [NSApp mainWindow], self, nil, nil, nil,
                                   @"You sneaky thing!");
}
if ((flags & NSFunctionKeyMask)) {
    NSBeginInformationalAlertSheet(@"Modifier keys fn detected", nil, nil, nil, [NSApp mainWindow], self, nil, nil, nil,
                                   @"You sneaky thing!");
}
uchuugaka
  • 12,679
  • 6
  • 37
  • 55
12

Here is the code deals with key event with swift 3.0.1 tested on Xcode 8.2.1 and macOS 10.12.2

override func keyDown(with event: NSEvent) {
    var handled = false
    if event.keyCode == 53 { // ESC, same as `CMD + .`
        handled = true
        print("ESC")
    }
    if event.modifierFlags.contains(.command) { // .shift, .option, .control ...
        if let chars = event.charactersIgnoringModifiers {
            handled = true // likely we are interested with that key
            switch chars {
            case "r":
                print("CMD + r")
            case ",":
                print("CMD + ,")
            case "/":
                print("CMD + /")
            default:
                handled = false
            }
        }
    }
    if !handled {
        super.keyDown(with: event) // let system handle it(may contains sound)
    }
}
longkai
  • 3,598
  • 3
  • 22
  • 24
  • Pre-swift 3 answers are all now deprecated. Besides `NSShiftKeyMask` there use to also be a `NSAlphaShiftKeyMask` (see https://developer.apple.com/reference/appkit/nseventmodifierflags) -- I am replacing the former with `.shift`, but what about the later? Is there a Swift 3 equivalent. – wcochran Apr 03 '17 at 18:02
  • @wcochran Hello, according Apple's source code, in *AppKit.NSEvent.swift*, line 307(Xcode 8.2.1), `@available(OSX, introduced: 10.0, deprecated: 10.12, renamed: "NSEventModifierFlags.capsLock") public let NSAlphaShiftKeyMask: NSEventModifierFlags` So use `.capsLock` instead. – longkai Apr 03 '17 at 19:28
2

What do you do if the key press does not come as an event? For example, you want to test the state of the Shift key when the program starts up? The Shift key was pressed before the program started, and will not be released until after you test its state.

In Windows, you can easily get this with a call to GetKeyState(VK_SHIFT). What is the macOS equivalent?

Pierre
  • 4,114
  • 2
  • 34
  • 39
  • 2
    `NSEvent.modifierFlags` class method returns you current modifier key states at any time. I can't find a clear way to subscribe to changes of this variable though. – beefon Aug 18 '20 at 07:26
  • I use this little helper class... ` import AppKit public class KeyboardHelper { public static var optionKeyIsDown: Bool { let flags = NSEvent.modifierFlags return flags.contains(.option) } public static var shiftKeyIsDown: Bool { let flags = NSEvent.modifierFlags return flags.contains(.shift) } } ` so then all I need is ` KeyboardHelper.shiftKeyIsDown ` – Paul Stevenson Nov 16 '22 at 23:04
2

You want to look, if a user presses a special key, regardless if any normal keys are pressed at the same time. In Swift 5 you use an override func in AppDelegate.

The placeholder in a new Swift project should look like this:

override func flagsChanged (with theEvent: NSEvent)
{
    // ...
}

Now you fill this empty func with code like this:

override func flagsChanged (with theEvent: NSEvent)
{
    let mods: Int = (Int(theEvent.modifierFlags.rawValue)) >> 16
    let t_KeyCapsLockPressed = (bitand(mods, 1) != 0)
    let t_KeyShiftPressed = (bitand(mods, 2) != 0)
    let t_KeyControlPressed = (bitand(mods, 4) != 0)
    let t_KeyOptionPressed = (bitand(mods, 8) != 0)
    let t_KeyCommandPressed = (bitand(mods, 16) != 0)
    // let t_KeyNumericPadPressed = (bitand(mods, 32) != 0) // Not used here
    // let t_KeyHelpPressed = (bitand(mods, 64) != 0) // Not used here
    let t_KeyFnPressed = (bitand(mods, 128) != 0)
    print("flagsChanged: t_KeyShiftPressed = \(Int(t_KeyShiftPressed))")
    print("flagsChanged: t_KeyControlPressed = \(Int(t_KeyControlPressed))")
    print("flagsChanged: t_KeyOptionPressed = \(Int(t_KeyOptionPressed))")
    print("flagsChanged: t_KeyCommandPressed = \(Int(t_KeyCommandPressed))")
    // and so on...
}

The func is called every time a special key is pressed or released. So you can do whatever you want. If you declare the variables global, you can access them from everywhere in real time:

// In the header of your program:

var t_KeyCapsLockPressed: Bool = false
var t_KeyShiftPressed: Bool = false
var t_KeyControlPressed: Bool = false
var t_KeyOptionPressed: Bool = false
var t_KeyCommandPressed: Bool = false
var t_KeyFnPressed: Bool = false
var t_KeyFlagsHaveChanged: Bool = false

// In the AppDelegate:

override func flagsChanged (with theEvent: NSEvent)
{
    let mods: Int = (Int(theEvent.modifierFlags.rawValue)) >> 16
    t_KeyCapsLockPressed = (bitand(mods, 1) != 0)
    t_KeyShiftPressed = (bitand(mods, 2) != 0)
    t_KeyControlPressed = (bitand(mods, 4) != 0)
    t_KeyOptionPressed = (bitand(mods, 8) != 0)
    t_KeyCommandPressed = (bitand(mods, 16) != 0)
    t_KeyFnPressed = (bitand(mods, 128) != 0)
    t_KeyFlagsHaveChanged = true
}

// In your Main program:

func keyCheck ()
{
    if t_KeyFlagsHaveChanged
    {
        print("flagsChanged: t_KeyShiftPressed = \(Int(t_KeyShiftPressed))")
        print("flagsChanged: t_KeyControlPressed = \(Int(t_KeyControlPressed))")
        print("flagsChanged: t_KeyOptionPressed = \(Int(t_KeyOptionPressed))")
        print("flagsChanged: t_KeyCommandPressed = \(Int(t_KeyCommandPressed))")
    // and so on...
       t_KeyFlagsHaveChanged = false
   }
}
j.s.com
  • 1,422
  • 14
  • 24
-3

Checking for pressed:

-(void)keyDown:(NSEvent *)theEvent
{
    if ([theEvent modifierFlags] & NSShiftKeyMask)
    {
        NSLog("Shift key was pressed");
    }
}

Checking for release:

-(void)keyUp:(NSEvent *)theEvent
{
    if(!([theEvent modifierFlags] & NSShiftKeyMask))
    {
        NSLog("Shift key was released");
    }
}

It should be noted, the NSLog function will only be called if SHIFT and then some other key is pressed.

Hope this helps!

Nolan Anderson
  • 576
  • 1
  • 6
  • 15
  • I'm afraid it wouldn't: this is *exactly* what I was doing (per the question), and I needed to know without having to press another key. – zneak Apr 11 '14 at 19:22
-4

in the apple's documentation :

You have to override the keydown method

with the following NSEvent modifier flags

moxy
  • 1,634
  • 1
  • 11
  • 19
  • 1
    I am already overriding the `keyDown` method from `NSView` (in turn from `NSResponder`) and the method simply isn't called when I press the shift key. (I updated my question to reflect this; I now see that it could have been a source of confusion.) – zneak Feb 13 '12 at 21:21
  • did you look at the **Keyboard Interface Control** part ? Maybe the shift even without other key pressed at the same time is intercepted by it ? If you press shift + A, do you see the shift key pressed ? – moxy Feb 13 '12 at 21:33