I was tired of all the answers to do it differently, so I put my nose down and figured out a way to make this work. This does not use the keydown event directly, but it uses keydown in the block. And the behavior is exactly what I wanted.
Text box subclass
.h
@interface LQRestrictedInputTextField : NSTextField
.m
In setting up the first responder, a local event
static id eventMonitor = nil; - (BOOL)becomeFirstResponder { BOOL okToChange = [super becomeFirstResponder]; if (okToChange) { [self setKeyboardFocusRingNeedsDisplayInRect: [self bounds]]; if (!eventMonitor) { eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:^(NSEvent *event) { NSString *characters = [event characters]; unichar character = [characters characterAtIndex:0]; NSString *characterString=[NSString stringWithFormat:@"%c",character]; NSArray *validNonAlphaNumericArray = @[@" ",@"(",@")",@"[",@"]",@":",@";",@"\'",@"\"",@".",@"<",@">",@",",@"{",@"}",@"|",@"=",@"+",@"-",@"_",@"?",@"#", @(NSDownArrowFunctionKey),@(NSUpArrowFunctionKey),@(NSLeftArrowFunctionKey),@(NSRightArrowFunctionKey)]; if([[NSCharacterSet alphanumericCharacterSet] characterIsMember:character] || character == NSCarriageReturnCharacter || character == NSTabCharacter || character == NSDeleteCharacter || [validNonAlphaNumericArray containsObject:characterString ] ) {
delete the event after editing the text field.
Also, if you use ARC, I noticed that you might need to assign a textview string to stringValue. I have nslog'd stringValue and the value has been saved. Without nslog, I had to assign a string to the notification object in stringValue so that it would not be freed.
-(void) textDidEndEditing:(NSNotification *)notification { [NSEvent removeMonitor:eventMonitor]; eventMonitor = nil; NSTextView *textView=[notification object]; self.stringValue=textView.string; }