In a toy project, I had the need to intercept all external keyboard keypresses.

Something I am always trying to keep a critical eye on when coding in swift is 'Have I made this as concise as possible without sacrificing readability in the name of cleverness/magic/etc? I want to be sure to jettison any overly verbose habits from coding in Objective-C for the last 5 years. Elegance and readability are critical in creating easily maintainable code.

In order to intercept external keyboard keypresses in iOS, one has to implement a function

func keyCommands() -> [UIKeyCommand]!

in a ViewController

From that function you return an array of valid UIKeyCommand's. When iOS receives a keypress, it checks, in order, your array of valid UIKeyCommand's, and if it finds a match, invokes a method on the class to alert you to the keypress.

If you wanted to invoke the 'keyPressed:' method on your class when Command-A was pressed, you would create a UIKeyCommand instance:

UIKeyCommand("A", UIKeyModifierFlags.Command, Selector("keyPressed:"))

And to build an entire keyboard worth of possible keypresses, you could make an Array of characters, and put them all together like this:

var keys = [UIKeyCommand]() for digit in Array("ABCDEFGHIJKLMNOPQRSTUVWXYZ") { keys.append(UIKeyCommand(input: String(digit), modifierFlags: UIKeyModifierFlags.Command, action: Selector("keyPressed:")) keys.append(UIKeyCommand(input: String(digit), modifierFlags: UIKeyModifierFlags.Control, action: Selector("keyPressed:")) keys.append(UIKeyCommand(input: String(digit), modifierFlags: nil, action: Selector("keyPressed:")) }

How can we make this code more concise?

It turns out swift offers a number of things out of the box like Strings are already enumerable for their elements (drop Array())

var keys = [UIKeyCommand]() for digit in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" { keys.append(UIKeyCommand(input: String(digit), modifierFlags: .Command, action: Selector("keyPressed:")) keys.append(UIKeyCommand(input: String(digit), modifierFlags: .Control, action: Selector("keyPressed:")) keys.append(UIKeyCommand(input: String(digit), modifierFlags: nil, action: Selector("keyPressed:")) }

and dropping Enum prefixes (UIKeyModifierFlags in this case):

var keys = [UIKeyCommand]() for digit in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" { keys.append(UIKeyCommand(input: String(digit), modifierFlags: .Command, action: Selector("keyPressed:")) keys.append(UIKeyCommand(input: String(digit), modifierFlags: .Control, action: Selector("keyPressed:")) keys.append(UIKeyCommand(input: String(digit), modifierFlags: nil, action: Selector("keyPressed:")) }

and automatic String to Selector conversion (drop Selector())

var keys = [UIKeyCommand]() for digit in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" { keys.append(UIKeyCommand(input: String(digit), modifierFlags: .Command, action: "keyPressed:")) keys.append(UIKeyCommand(input: String(digit), modifierFlags: .Control, action: "keyPressed:")) keys.append(UIKeyCommand(input: String(digit), modifierFlags: nil, action: "keyPressed:")) }

and of course we could only convert the letter Character to a String type once:

var keys = [UIKeyCommand]() for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" { let sletter = String(letter) keys.append(UIKeyCommand(input: sletter, modifierFlags: .Command, action: "keyPressed:") keys.append(UIKeyCommand(input: sletter, modifierFlags: .Control, action: "keyPressed:") keys.append(UIKeyCommand(input: sletter, modifierFlags: nil, action: "keyPressed:") }

Ok . . . this is looking a little bit more concise than Objective-C, and fairly easy to read although you could argue, and I would, that passing a String to reference a function callback seems brittle. Alas it's a carry over from Objective-C interoperability.

How can we make this code tighter?

UIKeyCommand is a fairly unambiguous class. It's purpose is to hold a key w/modifiers and the action it should trigger. In this case, those parameter names ('input', 'modifierFlags', 'action:') don't seem to be adding much value to the readability of our code.

So it seems appropriate here to cut things down even further by creating a new convenience constructor for UIKeyCommand which allows using that same original constructor, but without the parameter names.

extension UIKeyCommand { convenience init( _ input: String!, _ modifierFlags: UIKeyModifierFlags, _ action: Selector) { self.init(input: input, modifierFlags: modifierFlags, action: action) } }

The underscores ( _ ) are what makes this work. Swift allows you to specifiy a outwardly visible parameter name seperately from parameter name that is inwardly referencable in the function. Underscore ( _ ) basically means 'Allow nothing' here.

With this new nameless parameter convenience constructor, our code can be further refined to drop input: and modifierFlags: and action::

var keys = [UIKeyCommand]() for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" { let sletter = String(sletter) keys.append(UIKeyCommand(sletter, .Command, "keyPressed:")) keys.append(UIKeyCommand(sletter, .Control, "keyPressed:")) keys.append(UIKeyCommand(sletter, nil, "keyPressed:")) }

Because the parameters .Command, .Control, 'keyPressed:', and sletter are so recognizable for their intent, I would argue we have only gained readability and conciseness here.

A little bit more tidyness can be attained by using the built in swift operator overload += (part of Array)

var keys = [UIKeyCommand]() for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" { let sletter = String(sletter) keys += [UIKeyCommand(sletter, .Command, "keyPressed:"), UIKeyCommand(sletter, .Control, "keyPressed:"), UIKeyCommand(sletter, nil, "keyPressed:")] }

Another area that could be tightened up is converting of the String to an Array, looping through the Sequence of Characters, and then converting each Character to a String. It is necessary but does not seem core to the problem we're solving here. We can extract those gruntwork details into a String extension to further simplify our code.

Adding a String extension:

extension String { func each(closure: (String) -> Void ) { for digit in self { closure(String(digit)) } } }

allows us to further refine our code to:

var keys = [UIKeyCommand]() "ABCDEFGHIJKLMNOPQRSTUVWXYZ".each({ keys += [UIKeyCommand($0, .Command, "keyPressed:"), UIKeyCommand($0, .Control, "keyPressed:"), UIKeyCommand($0, nil, "keyPressed:")] })

In swift, $0 is a special keyword that means 'the first parameter passed to the closure' which in this case would be each letter contained in the A-Z String.

Happy Swifting!

UPDATE: I wrote a follow-up to this blog entry here which covers some awesome feedback I received and new ideas.