Inspired by the Gmail Tap April Fools joke by Google I felt inspired to program the same thing for iOS. There we have custom input views as well as the UIKeyInput protocol and so I figured it should be an easy undertaking.
The whole affair took slightly more than one hour and I was hoping to record it in 1 second intervals with ScreenNinja. Unfortunately it seems that this otherwise fabulous app crapped out on me. I later discovered that the MOV file had actually finished before the crash, so to my delight (and hopefully yours too) you can follow this tutorial on YouTube.
In the video you see the whole process from start to finish. Let me just offer some clarifications in this post so that you better understand what I am doing.
In iOS Apple provides us with two protocols, UIKeyInput for inputting single key strokes and UITextInput for everything that’s more than single keys, like text selection, auto correction etc. As part of UITextInput each conforming UIView implements an inputView property that can be any UIView.
If set this inputView is shown instead of the standard keyboard, if not then you see the keyboard. So for our example all we need is a UIView with one button for the Morse button.
The Gmail Tap prank interface uses two buttons, one for a dot, one for a dash. When researching Morse Code on Wikipedia I found one slight problem with this approach. Individual letters have to have a slight pause from each other because otherwise we wouldn’t know if by a dot the user intends to write an E or one of the other dozen or so combinations that have a dot as the first part of a sequence.
The Morse alphabet doesn’t just consist of 2 letters, but rather it uses 3 which are “dit”, “dah” and a silent pause. Given that “dit” is the shortest element, a “dah” is three times as long. Sequence elements are spaced one “dit” apart, Letters are spaced one “dah” apart and the length of 7 “dits” signifies a word break. Really, I am not making this up!
First step is to override canBecomeFirstResponder and return YES.
For simplicy I just wired up the UIControlEventTouchUpInside and the UIControlEventTouchDown to two methods and I’m using the time interval between the touch down and the touch up to determine if we saw a “dit” or “dah”. A bit of pause handling with performSelector:withObject:afterDelay: was necessary to get the breaks decoded.
Then there is a lookup table from which to draw the actual letters once a letter break is encountered.
+ (void)initialize { morseLookup = [NSDictionary dictionaryWithObjectsAndKeys:@"a", @"··−·", @"b",@"−···", @"c",@"−·−·", @"d",@"−··", @"e",@"·", @"f",@"··−·", @"g",@"−−·", @"i",@"··", @"j",@"·−−−", @"k",@"−·−", @"l",@"·−··", @"m",@"−−", @"n",@"−·", @"o",@"−−−", @"p",@"·−−·", @"q",@"−−·−", @"r",@"·−·", @"s",@"···", @"t",@"−", @"u",@"··−", @"v",@"···−", @"w",@"·−−", @"x",@"−··−", @"y",@"−·−−", @"z",@"−−··", @"0",@"−−−−−", @"1",@"·−−−−", @"2",@"··−−−", @"3",@"···−−", @"4",@"····−", @"5",@"·····", @"6",@"−····", @"7",@"−−···", @"8",@"−−−··", @"9",@"−−−−·", nil]; for (NSString *oneSequence in [morseLookup allKeys]) { _longestMorseSequence = MAX(_longestMorseSequence, [oneSequence length]); } } |
The final piece of the puzzle is that we somehow need to have a pointer to the text control so that we can insert the detected characters. I was puzzled by the fact there does not seem to be an obvious method to do that. So I went for the obvious method of setting it in the init.
The input delegate (e.g. a UITextField) has to be compliant with the UIKeyInput protocol and therefore we know that it must have an insertText: method. This will insert the passed string at the current cursor position.
Conclusion
Turns out the by far hardest part of making our own custom keyboard is the input logic, which itself becomes way more complicated by having to invent algorithms to decode Morse sequences.
There are several improvements that spring to mind right away. Auto-Completion would be one where it could show a decreasing number of possible sequences based on the sequence for a single letter so far. Maybe that could be shown if the finger is lifted outside of the button.
Another question to tackle is how to actually delete text, the backspace key if you will. Maybe by doing a “dit” that begins on the button, but ends outside to the left of it?
Finally there should be no set amount of time for a “dit”, experienced Morse-o-graphers can have much shorter dits as long as the relation of dits, dats and pauses remains mostly constant. In this tutorial I went for the easiest method of simply using the latest dit’s duration, but it would be nice to average this over time.
Source code of the full project is available on GitHub.
Categories: Recipes
Hi, I like your tutorial very much, especially the video with all the steps you have gone through. This is the best example I have found about how to make a custom input view to replace the system keyboard. The source code can be downloaded as a zip file so I do not have to worry about the folder structures. But I still have a small request: could you publish it in app store? I really would like to try it on a real device. It will also make this tutorial complete: from source code to running it on your device. That would be a great experience for a new learner like me. Thank you.
Where’s the “h”? 😀
More uptodate version here, including h. https://github.com/Cocoanetics/DTMorseKeyboard/blob/master/DTMorseKeyboard/DTMorseKeyboard.m