brainbag
brainbag

Reputation: 1057

NSTextField - notifications when individual keys are pressed

I am making an app that will add sound to keypresses as the user types in an NSTextField. I need to capture keystrokes and know what each individual keypress is (like "d" or "space" or "6"). The app depends on this. There is no other way around it.

Each window is an NSDocument File Owner, and it has a single NSTextField in it, which is where the document data is parsed, and the user will type.

After hours of parsing the Internet for answers and hacking away at code, the four most commonly repeated answers are:

  1. "that is not how things work, here is (irrelevant answer)"
  2. "you are new to Cocoa, that is a bad idea, use control:textView:doCommandSelector:" that doesn't give me individual keys, and some keys need their own unique sound trigger.
  3. "use controlTextDidChange: or textView:shouldChangeTextInRange:replaceString:" controlTextDidChange doesn't give me individual keys, and the second one only works for textViews or UIKit.
  4. People get confused and answer with recommendations for UIKit instead of AppKit, which is iOS-only.

The weird thing is that if I subclass NSTextField, it receives -keyUp. I don't know where -keyDown is going.

So my ultimate question is: can you tell me some kind of step-by-step way to actually capture the keyDown that is sent to NSTextField? Even if it's a hack. Even if it's a terrible idea.

I would love to solve this problem! I am very grateful for your reading.

Upvotes: 5

Views: 2147

Answers (4)

Kevek
Kevek

Reputation: 2554

This is a pretty old question, but as I was trying to implement a NSTextField that could react to keyDown so that I could create a hotkey preferences control I found I wanted the answer to this question.

Unfortunately this is a pretty non-standard use and I didn't find any places that had a direct answer, but I've come up with something that works after digging through the documentation (albeit in Swift 4) and I wanted to post it here in case it helps someone else with a non-standard use case.

This is largely based off of the information gleaned from the Cocoa Text Architecture Guide


There are three components to my solution:

  1. Creating your NSWindowController and setting a NSWindowDelegate on your NSWindow:

    guard let windowController = storyboard.instanciateController(withIdentifier:NSStoryboard.SceneIdentifier("SomeSceneIdentifier")) as? NSWindowController else {
        fatalError("Error creating window controller");
    }
    if let viewController = windowController.contentViewController as? MyViewController {
        windowController.window?.delegate=viewController;
    }
    
  2. Your NSWindowDelegate

    class MyViewController: NSViewController, NSWindowDelegate {
        // The TextField you want to capture keyDown on
        var hotKeyTextField:NSTextField!;
        // Your custom TextView which will handle keyDown
        var hotKeySelectionFieldEditor:HotKeySelectionTextView = HotKeySelectionTextView();
    
        func windowWillReturnFieldEditor(_ sender: NSWindow, to client: Any?) -> Any? {
            // If the client (NSTextField) requesting the field editor is the one you want to capture key events on, return the custom field editor. Otherwise, return nil and get the default field editor.
            if let textField = client as? NSTextField, textField.identifier == hotKeyTextField.identifier {
                return hotKeySelectionFieldEditor;
            }
            return nil;
        }
    }
    
  3. Your custom TextView where you handle keyDown

    class HotKeySelectionTextView: NSTextView {
        public override func keyDown(with event: NSEvent) {
            // Here you can capture the key presses and perhaps save state or communicate back to the ViewController with a delegate pattern if you prefer.
        }
    }
    

I fully admit that this feels like a workaround somewhat, but as I am experimenting with Swift at the moment and not quite up to speed with all of the best practices yet I can't make an authoritative claim as to the "Swift-i-ness" of this solution, only that it does allow a NSTextField to capture keyDown events indirectly while maintaining the rest of the NSTextField functionality.

Upvotes: 2

Christian Krueger
Christian Krueger

Reputation: 139

controlTextDidChange is quite a good solution, but don't forget this 2 important things:

  1. Set the delegate binding of the textField to the object where you define the controlTextDidChange method. Commonly, in document based apps it is the window controller, otherwise your app delegate.

enter image description here

  1. Set the textField's control to "continous" in the attribute inspector section

enter image description here

If you miss those points, you will have no result.

Upvotes: 3

JWWalker
JWWalker

Reputation: 22707

Text editing for an NSTextField is handled by an NSTextView provided by the window, called the field editor. See the NSWindow method fieldEditor:forObject: and the NSWindowDelegate method windowWillReturnFieldEditor:toObject:. I suppose you could use one of these to provide your own subclassed NSTextView as the field editor. Or, could you simply use NSTextView instead of NSTextField?

Upvotes: 0

Hussain Shabbir
Hussain Shabbir

Reputation: 15015

Try like this if you print nslog you will get individual character record for example you pressd "A" you will get the same in console:-

-(void)controlTextDidChange:(NSNotification*)obj
{
NSLog(@"%@",[yourTextfield stringValue]);

}

Also, not sure this is only your requirement.

Upvotes: 0

Related Questions