Prior to iOS 8, developers could provide custom keyboards or supplement the system keyboard with custom keys within only their application. With iOS 8, Apple has made it possible to create custom keyboards that will be able to be used system wide in other apps. You can now ship a custom keyboard with your app and users will be able to choose it as the keyboard to use for every app that requires text input.
To create a successful keyboard that users are more likely to keep on using, you have to meet some of the expectations that they have for what a keyboard should do. Since they will have been using the system keyboard for a long time, it is a good place to look at when deducing what your keyboard design and functionality should be.
The system keyboard is fast, responsive and capable. It doesn’t interrupt the user with requests or information. Users have come to expect the following features from their keyboard. These are not requirements for creating custom keyboards, but just examples of what you could include in your keyboard to increase the chances of it being popular in a competitive market.
- Auto suggestion and auto correction
- Inserting period upon double space
- Caps lock support
- Keycap artwork
- Multistage input for ideographic languages
- Appropriate layout and features based on keyboard type trait for example presenting appropriate keys for easy email input when the user switches to an email field.
Custom Keyboard Limitations
There are some system keyboard features that are unavailable to custom keyboards. These include:
- Custom keyboards don’t have access to most of the general keyboard settings in the Settings app such as Auto-capitalization, Enable Caps Lock or dictionary reset. However you can provide your own settings bundle that can be displayed in Settings.app. You can check the Implementing an iOS Settings Bundle guide for more on this.
- A Custom Keyboard cannot be used to type into certain text input objects. These include the secure text input objects (any object that has its secureText property set to YES) and phone pad objects (any object that has a keyboard type trait of UIKeyboardTypePhonePad or UIKeyboardTypeNamePhonePad). When the user types in any of these text input objects, the system temporarily replaces your custom keyboard with the system keyboard, and on typing in a non-secure or non-phone pad object, your keyboard resumes.
- Input dictation isn’t possible for a custom keyboard since, like all extensions in iOS 8, it has no access to the device microphone.
- Selecting text is also not possible. Text selection is under the control of the app that is using the keyboard.
- Closely related to the above point, editing menu options i.e. Cut, Copy, Paste are inaccessible. If an app provides an editing menu interface, the keyboard has no access to it.
- You cannot display key artwork above the top edge of a custom keyboard’s primary view the same way Apple does when you tap and hold a key in the top view.
- App developers can reject the use of custom keyboards in their app. This can especially be done in apps that are sensitive to security such as banking apps.
Essential Features of Custom Keyboards
The Apple Extensions guide specifies two development essentials for every custom keyboard:
- Trust. A custom keyboard gives you access to what a user types. You should therefore establish and maintain their trust by being visible about privacy policies. Users should be assured that their keystroke data will only be used for the document they are typing and not for other purposes not obvious to them. If the keyboard employs use of other user data such as Location Service or Address Book database, then this should be conveyed to the user.
- Next Keyboard key.This is a mandatory feature that allows users to switch to another keyboard. On the system keyboard, this is seen as the button with a globe on it. You tap once to switch to the next keyboard and long press it to view available keyboards.
Creating the Keyboard
We are going to look at two ways to build a keyboard’s UI – programmatically and using an Interface Builder document. The method you pick is a matter of preference, but I find that if the keyboard UI is complicated, it is better to use an Interface Builder document where you’ll be able to use Auto Layout and preview the effects of the constraints you’ve added without running the app, while in the case of creating the UI in code, you’ll end up writing a lot of code to set up constraints, which you can only see the effects of by running the project. Debugging this can be a bit of a headache.
To get started, create a new iPhone Simple View Application. Name it AC Custom Keyboard. This will be the container app for the custom keyboard. All extensions must be bundled within a containing app, and the containing app must provide some functionality. Apple are strict on this and so you cannot just have a skeleton container app whose sole purpose is to distribute your extension.
Next we’ll add an extension target to the project. Click on the project in the Project Navigator then select Editor > Add Target > iOS > Application Extension > Custom Keyboard and click Next
On the next window, set AC Keyboard as the product name and leave the other fields as they are and click Finish. You’ll be prompted to activate the new targets’ scheme, click Cancel.
A new group will have been created on the Project Navigator with the name of your keyboard – AC Keyboard. In it is the keyboard’s view controller and a property list file. The Info.plist file contains settings for the keyboard. Notable ones are:
- Bundle display name – This is the display name for the keyboard, change it if you want a different name to be seen by users.
- isASCIICapable – This is a key found within the NSExtension/NSExtensionAttributes dictionary. It determines if the custom keyboard can support ASCII characters.
- PrefersRightToLeft – Specifies the direction of the primary language of the keyboard.
- PrimaryLanguage – The primary language of the keyboard. The default is en-US.
- RequestsOpenAccess – Determines the network access of the keyboard.
In Main.storyboard, add a TextView to the main View. Center it so that it completely covers the view. Run the application and bring up the keyboard by tapping anywhere on the screen. To change keyboards, tap the globe button on the system keyboard. If there is no globe button, tap the button with a smiley icon. If you want to see all available keyboards, hold the globe key and a list of keyboards will appear. Our keyboard isn’t on the list. You need to first add it in Settings.
Navigate to Settings > General > Keyboard > Keyboards > Add New Keyboard and select AC Custom Keyboard. This will add it to the list of available keyboards. Go back to your app and bring up the keyboard by tapping the text view. Tap and hold the globe key and select AC Keyboard from the list that pops up.
On switching from the system keyboard to the custom one, you will see an almost empty keyboard with only one button for the Next Keyboard.
To add some keys to the keyboard, open KeyboardViewController.swift and make the following changes.
In viewDidLoad() add the following at the bottom of the function right after the code for the next keyboard button.
let buttonTitles = ["Q", "W", "E", "R", "T", "Y"]
var buttons = createButtons(buttonTitles)
var topRow = UIView(frame: CGRectMake(0, 0, 320, 40))
for button in buttons {
topRow.addSubview(button)
}
self.view.addSubview(topRow)
addConstraints(buttons, containingView: topRow)
Next add the function below which will create buttons with titles of the strings passed into it.
func createButtons(titles: [String]) -> [UIButton] {
var buttons = [UIButton]()
for title in titles {
let button = UIButton.buttonWithType(.System) as UIButton
button.setTitle(title, forState: .Normal)
button.setTranslatesAutoresizingMaskIntoConstraints(false)
button.backgroundColor = UIColor(white: 1.0, alpha: 1.0)
button.setTitleColor(UIColor.darkGrayColor(), forState: .Normal)
button.addTarget(self, action: "keyPressed:", forControlEvents: .TouchUpInside)
buttons.append(button)
}
return buttons
}
This function iterates through an array of Strings and creates buttons with the respective titles. It also adds a target to each button so that when it is tapped, the function keyPressed() will be called. Next add this function.
func keyPressed(sender: AnyObject?) {
let button = sender as UIButton
let title = button.titleForState(.Normal)
(textDocumentProxy as UIKeyInput).insertText(title!)
}
Here you get the title of the tapped button and insert it at the insertion point of the current text input object via the textDocumentProperty. This is an object conforming to the UITextDocumentProxy protocol, which acts as a proxy between the keyboard and the text input object that summoned it.
Next we add the addConstraints() method which will add constraints to the buttons and the containingView.
func addConstraints(buttons: [UIButton], containingView: UIView){
for (index, button) in enumerate(buttons) {
var topConstraint = NSLayoutConstraint(item: button, attribute: .Top, relatedBy: .Equal, toItem: containingView, attribute: .Top, multiplier: 1.0, constant: 1)
var bottomConstraint = NSLayoutConstraint(item: button, attribute: .Bottom, relatedBy: .Equal, toItem: containingView, attribute: .Bottom, multiplier: 1.0, constant: -1)
var leftConstraint : NSLayoutConstraint!
if index == 0 {
leftConstraint = NSLayoutConstraint(item: button, attribute: .Left, relatedBy: .Equal, toItem: containingView, attribute: .Left, multiplier: 1.0, constant: 1)
}else{
leftConstraint = NSLayoutConstraint(item: button, attribute: .Left, relatedBy: .Equal, toItem: buttons[index-1], attribute: .Right, multiplier: 1.0, constant: 1)
var widthConstraint = NSLayoutConstraint(item: buttons[0], attribute: .Width, relatedBy: .Equal, toItem: button, attribute: .Width, multiplier: 1.0, constant: 0)
containingView.addConstraint(widthConstraint)
}
var rightConstraint : NSLayoutConstraint!
if index == buttons.count - 1 {
rightConstraint = NSLayoutConstraint(item: button, attribute: .Right, relatedBy: .Equal, toItem: containingView, attribute: .Right, multiplier: 1.0, constant: -1)
}else{
rightConstraint = NSLayoutConstraint(item: button, attribute: .Right, relatedBy: .Equal, toItem: buttons[index+1], attribute: .Left, multiplier: 1.0, constant: -1)
}
containingView.addConstraints([topConstraint, bottomConstraint, rightConstraint, leftConstraint])
}
}
Run the app and you should see a similar keyboard as shown below
As you can see, adding views programmatically requires you to set up constraints in code and if your keyboard’s UI is complicated and has many keys, this can get complicated and hard to debug. Next we will look at using the Interface Builder to create buttons.
Create a nib file by navigating to File > New > File > iOS > User Interface > View. Name it KeyboardView and make sure it is under the AC Keyboard target.
Select the nib’s view and in the Attributes Inspector, set its Size as Freeform and Status Bar to None. Then go to the Size Inspector and set its width to 320 and height to 220.
In KeyboardViewController.swift add the following in viewDidLoad() after the call to super.viewDidLoad(). This sets the nib file as the view of the view controller.
let nib = UINib(nibName: "KeyboardView", bundle: nil)
let objects = nib.instantiateWithOwner(self, options: nil)
view = objects[0] as UIView;
Add a view to the nib’s main view. Click on the Pin menu at the bottom of the Interface Builder interface and give it the following constraints – Height of 40, Trailing space of 0, Leading space of 0 and Top space to container of 42. Make sure Constrain to margin is not checked.
Add 9 buttons to the view. Set their text color to Dark Gray and change their titles to these letters respectively: ‘A’, ‘S’, ‘D’, ‘F’, ‘G’, ‘H’, ‘J’, ‘K’, ‘L’. Now for the constraints.
With each button, select the Align menu and select Vertical Center in Container.
Select the A button and add a Leading space to Superview fo 5. Select L and add a Leading space to Superview of 5. Control-drag from A to S and select Horizontal Spacing. We’ll change its value later. Do the same with S to D, D to F, F to G, and so on.
Select A and in the Size Inspector, make sure that its Leading Space to S ie set to a constant of 5. Select the next letter and set its Trailing space to the letter following it to 5. When you are done, all letters should have a Leading Space and Trailing space set to 5. Select the main view then Editor > Resolve Auto Layout Issues > All Views > Update Frames.
Run the application and you should see the keys you just added set up below the ones you added in code.
To create outlets and actions for the keys, select the File’s Owner from the Document Outline, then in the Identity Inspector, set the class as KeyboardViewController. You can then create actions and outlets like you usually do in storyboard files by Control-dragging from a control to the view controller class. (You’ll see more of this in the example that follows).
Now that we have seen how to create a keyboard’s UI programmatically and with a nib file, let us add some functionality to it. For this, I have a starter project that we will be using. The project is a of a simple keyboard that we will add some functionality to. It is shown below. You should note that to keep it simple, I didn’t design for all size classes so, while it looks nice on the iPhone 5S, it doesn’t look quite the same for bigger screens. You can download the code here. Also note that the keyboard’s name is Appcoda Keyboard and not the AC Keyboard we had earlier.
I have already set up the actions and outlets we’ll require, but haven’t written the code for them (except for the Next Keyboard key).
First, you’ll notice that the Next Keyboard key has been replaced with a key titled KB. The action method for this can be seen it the view controller file as shown below.
@IBAction func nextKeyboardPressed(button: UIButton) {
advanceToNextInputMode()
}
We are first going to set up the actions for keys with letters and symbols i.e. any key you tap and see its title as the typed text. I created an action method for all these keys called keyPressed(). Modify this method as shown.
@IBAction func keyPressed(button: UIButton) {
var string = button.titleLabel!.text
(textDocumentProxy as UIKeyInput).insertText("\(string!)")
}
This is similar to what we had before. The title of the button is inserted at the insertion point of the current text input object via the textDocumentProperty. All the letters we type will be in Caps, but we will fix this shortly. Next modify the following functions to set up actions for the backspace(BS), space(SPACE) and return(RTN) keys respectively.
@IBAction func backSpacePressed(button: UIButton) {
(textDocumentProxy as UIKeyInput).deleteBackward()
}
@IBAction func spacePressed(button: UIButton) {
(textDocumentProxy as UIKeyInput).insertText(" ")
}
@IBAction func returnPressed(button: UIButton) {
(textDocumentProxy as UIKeyInput).insertText("\n")
}
Run the app and test the keys.
In the view file, you will notice two views labelled Char Set 1 and Char Set 2. These are on the same row with one on top of the other. In viewDidLoad() the second view is hidden. Modify the charSetPressed() function as shown so that when the user presses the key labelled 1/2, the key’s text will change to 2/2 and a new set of characters will appear on the first row of the keyboard.
@IBAction func charSetPressed(button: UIButton) {
if button.titleLabel!.text == "1/2" {
charSet1.hidden = true
charSet2.hidden = false
button.setTitle("2/2", forState: .Normal)
} else if button.titleLabel!.text == "2/2" {
charSet1.hidden = false
charSet2.hidden = true
button.setTitle("1/2", forState: .Normal)
}
}
If you look at the system keyboard, there is usually an indication, in the form of a brief animation when you tap a key. We should add some sort of feedback so the user knows that they tapped the right key. Add the following at the end of the keyPressed() method.
UIView.animateWithDuration(0.2, animations: {
button.transform = CGAffineTransformScale(CGAffineTransformIdentity, 2.0, 2.0)
}, completion: {(_) -> Void in
button.transform =
CGAffineTransformScale(CGAffineTransformIdentity, 1, 1)
})
This makes a key scale up briefly when tapped before going back to its original size.
Lastly we’ll implement the Capslock key(CL). Modify the capsLockPressed() function as follows.
@IBAction func capsLockPressed(button: UIButton) {
capsLockOn = !capsLockOn
changeCaps(row1)
changeCaps(row2)
changeCaps(row3)
changeCaps(row4)
}
Notice that at the beginning of the file, we set capsLockOn to true. The above method toggles this value to be true/false and then calls a function that will change the case of the typed text as well as the title of the buttons. Add the following method to the class.
func changeCaps(containerView: UIView) {
for view in containerView.subviews {
if let button = view as? UIButton {
let buttonTitle = button.titleLabel!.text
if capsLockOn {
let text = buttonTitle!.uppercaseString
button.setTitle("\(text)", forState: .Normal)
} else {
let text = buttonTitle!.lowercaseString
button.setTitle("\(text)", forState: .Normal)
}
}
}
}
Run the app and you should be able to change the case of the letters.
Conclusion
This tutorial doesn’t cover all that you can do with the keyboard extension. There is still a lot you can do to make the keyboard better. You can have it detect the beginning of sentences so that the first letter of the next typed word is capitalized, you can add auto-correction, auto-completion e.t.c. This is a new technology on the Apple platform, and it will be interesting to see what developers come up with.
For your reference, you can download the Xcode project here.