Key Events and the Responder Chain: Sample Code using arrow keys in custom NSTextField

 

My latest adventure left me using a NSStepper to increment and decrement a NSTextField, just making the numbers go up or down by one. I set the minValue to 1 and bound the maxValue to an array controller I have, with the appropriate data. All of this works fine, including the -valueWraps behavior to always keep the value in the correct range. However, I would like to have keyboard events do the same thing as the mouse events, so I rigged up a simple custom NSTextField that intercepts keyUp: events and conforms to the -valueWraps behavior set in the NSStepper itself.

Read on to see some snippets of code with an explanation and download a sample project.

-(void)keyUp:(NSEvent *)theEvent {if ([theEvent modifierFlags] & NSNumericPadKeyMask) {
NSString *theArrow = [theEvent charactersIgnoringModifiers];

In this first bit, we are filtering out only keys pressed that are part of the numeric keypad.
Arrow keys fall into this category. The string is a string of keys pressed.

if ( [theArrow length] == 0 ) {
return; // reject dead keys
}if ( [theArrow length] == 1 ) {
unichar keyChar = [theArrow characterAtIndex:0];

Now we have a char that represents the key pressed, an arrow key still

if ( keyChar == NSUpArrowFunctionKey ) {// Setup the loop, wrapping action between bounds
if ( [[self stringValue] doubleValue] < [stepper maxValue] ) {
NSNumber *value = [NSNumber numberWithInt:[[self stringValue] intValue] + 1];
[self setStringValue:[value stringValue]];
} else if ( [[self stringValue] doubleValue] >= [stepper maxValue] ) {
[self setStringValue:@">
}
}

The above deals with the user hitting the up arrow key and the below deals with user hitting the down arrow key

else if ( keyChar == NSDownArrowFunctionKey ) {// Setup loop, wrapping action between bounds
if ( [[self stringValue] doubleValue] > 1 ) {
NSNumber *value = [NSNumber numberWithInt:[[self stringValue] intValue] – 1];
[self setStringValue:[value stringValue]];
} else if ( [[self stringValue] doubleValue] <= 1 )
[self setStringValue:[NSString stringWithFormat:@”>
}

Essentially, I set a NSTextField’s custom class to my StepperTextField. Then I set the outlet from the text field to a NSStepper I already have configured in the interface, correct with maxValue and minValue set for the NSStepper in IB. Of note, I use the keyUp: method because the keyDown: method is being captured by the field editor inside the NSTextField, so I never catch those events. This seemed like the simplest approach to me. Now when the user hits a down arrow, the number will decrement by one. An up arrow increments it by one. If the value will be over the maxValue, it gets set to 1. If it will be less than 1, it gets set to the maxValue.

Download a sample project with plenty of comments here.

Comments (6) Leave a Comment
  1. r. October 12th, 2007 at 19:31 | #1

    Thanks for the sample code..

  2. r. October 12th, 2007 at 19:54 | #2

    Actually your code as provided does not work on a laptop, which has no numeric keypad. Since the codes you’re matching on are NS*FunctionKey types, I replaced the mask with NSFunctionKeyMask and that seems to work fine.

  3. r. October 18th, 2007 at 22:53 | #3

    Here’s a way cleaner version that works on keyDown, supports autorepeat, doesn’t affecting the insertion point or selection, _and it does away with all your string conversion funkiness.

    -(void)awakeFromNib {
    [self setIntValue:1];

    [self setDelegate:self];
    }

    -(BOOL)
    control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command {
    BOOL result = NO;

    if (command == @selector(moveUp:)) { // up arrow pressed
    double increment = [self doubleValue] >= [stepper maxValue] ? [stepper minValue] : [self doubleValue] + [stepper increment];
    [self setIntValue:increment];

    result = YES;

    } else if (command == @selector(moveDown:)) { // down arrow pressed
    double decrement = [self doubleValue]

  4. r. October 18th, 2007 at 22:55 | #4

    Whoops, the html form gremlins ate the rest of the code. I’m sure you can figure out the rest of the down arrow case, and when that’s done just return result. For bonus points check to make sure stepper is valid before using it.

  5. Matt October 19th, 2007 at 09:28 | #5

    Awesomeness, thanks for the code suggestions. I’ll be playing more with that if I ever find time to play in Cocoa instead of the devilish C++ that I’m using for all my classes…

  6. Matt October 22nd, 2007 at 16:29 | #6

    hey, thanks a bunch, I have been creating (TRYING) a program, unfortunately, I am a noob so it takes me a while to make it but your tut was very helpful; about NSSteppers. If you want my program when I am done, email me at mamiller93@gmail.com :)

Allowed tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">
  1. No trackbacks yet.