Last Updated: December 26, 2018
·
11K
· kerrishotts

Responding to Keyboard Events on iOS

A beta tester recently asked if I could implement some keyboard shortcuts beyond the standard select, copy, paste, etc. I didn't think it was feasible at the time, but with some help from @antijingoist (https://alpha.app.net/antijingoist), and this post on Stack Overflow reminding me of the inputView property (http://stackoverflow.com/questions/3397339/keyboard-shortcuts-in-ios), I found I could respond to regular keystrokes. By regular, I mean letters, numbers, etc. The arrow keys, unfortunately, do not work.

Here's how I did it, and you're welcome to try it, improve it, criticize it to no end -- what ever you want. I'm sure this isn't the best, or even right way to do it, but it appears to be working so far for me. [Am I sure this is Apple-Approved? No. So if your app gets rejected because of it, you've been warned.]

First, I made my viewController adhere to the UIKeyInput protocol:

@interface myViewController: UIViewController <UIKeyInput>
...

Second, near the end of my controller's code, I added these methods:

-(void) deleteBackward: (id)sender
{
  return;
}

-(BOOL) hasText
{
  return YES;
}

-(void) insertText:(NSString *)text
{
  static NSDate *lastKeypress;
  NSTimeInterval lastKeypressInterval = [lastKeypress timeIntervalSince1970];  
  NSDate *thisKeypress = [NSDate date];
  NSTimeInterval thisKeypressInterval = [thisKeypress timeIntervalSince1970];
  if (thisKeypressInterval > lastKeypressInterval + 0.5)
  {
    lastKeypress = thisKeypress;
    if ([text isEqualToString:@"W"])
    {
      // go to top of page
    }
    if ([text isEqualToString:@"w"])
    {
      // go up a row
    }
    ...
  }
...
}

The idea here is since I can't capture arrow keys, I'll do with WASD instead. It makes sense to me to have a shift+W go to the top, while a regular unshifted W just go up a little bit, hence the difference above, but you could do the same thing regardless of the case.

Now, I've got some debounce checking going on here since sometimes keys seemed to fire off the code more than once. Restricting the response to every 500ms seemed to help, but if you need better response, you'll have to turn this down or eliminate it entirely.

Next your viewController needs to also indicate that it can be a first responder:

-(BOOL) canBecomeFirstResponder
{
  return YES;
}

And your view needs to become a first responder at some point (say viewDidAppear):

[self becomeFirstResponder];

Everything works really well -- but for one thing: that pesky soft keyboard that gets in the way. We could adjust our UI to allow for it, but why? We're implementing keyboard shortcuts, and without a hardware keyboard attached, touch is far more preferable.

Turns out, we can override inputView. First, in your .h file:

@property (strong, nonatomic, readwrite) UIView * inputView;

And then in your controller's viewDidLoad:

self.inputView = [[UIView alloc] initWithFrame: CGRectMake(0,0,0,0)];

Guess what -- the soft keyboard goes away (since it tries to display a view with no width or height), but we still get the events.

So even though we don't get arrow keys, I'll take this for now. It should meet the needs of my advanced users without getting in the way of those users who don't care about these things at all.

If you've got a better way or know if Apple will reject apps that do this, I'd be happy to know.

Oh, and you can follow me @photokandy on Twitter or App.net if you'd like to chat.