iOS 8 brings a lot of cool features along with it. One of them being the addition of 3rd Party Keyboards as App Extensions. And we should really pay attention this time, as it opens up a whole new category of apps and monetization options. With over a million apps in the Store, I will welcome a new category any day 
With this post, I want to show you how to create a Custom Keyboard for your app which can be used system-wide as a keyboard option.
This tutorial will be done in Swift. It was my first real project using the Swift language and I’m loving it.. “for now”. Let’s jump right in.
First off, here is a screenshot of what we are going to end up building. The keyboard will be able to enter text into a text field, delete text and some other basic functions. Advanced features like predictive text, Lexicons etc and out of the scope of this tutorial.
Create Your Xcode Project
Open up Xcode 6 and create a new project. File->New->Project.
Give your project the name CustomKeyboardSample and save it in a suitable location. This is going to be our host project, we still need to add an extension.
For now, lets add a Text Field to the project. Open the Main.Storyboard file and drag a UITextField to the view controller on the stage.
This is where we are going to be testing our inputs. Now it is time to add out extension
Click on File-> New -> Target, select Custom Keyboard which is listed under iOS/Application Extension. Give it the name CustomKeyboard and select, the Swift language.
Now you should have a folder called CustomKeyboard as the new target, with a file called KeyboardViewController.swift. Xcode has added some initial code in for us and this keyboard should be ready to go (albeit with no functionality yet)…
You can now try running the CustomKeyboardSample app and try out your new keyboard.
When you tap in the text field, the system keyboard will show up. We can switch keyboards using the Globe icon in the bottom row, but we need to install our new keyboard before it will show up.
Go to the Home screen. (Click on the Menu bar, Hardware->Home). Open the Settings app and navigate to General-Keyboard-Keyboards. Tap on “Add New Keyboard” and select CustomKeyboard. Turn on the switch to enable it and accept the warning message.
Tap done and you are are ready to go.
If run the app again in the simulator, you will now be able to switch the keyboard. Tap the Globe icon until you see a keyboard with just the “Next Keyboard” button.
Now it is time to start adding our own keys.
Open the file KeyboardViewController.h . In this file, you will see a class KeyboardViewController which inherits from UIInputViewController. This is the class that manages the view for the keyboard. We can add out buttons to the containing view and it will show up in the keyboard.
Add a function called createButton
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| func createButtonWithTitle(title: String) -> UIButton {
let button = UIButton.buttonWithType(.System) as UIButton
button.frame = CGRectMake(0, 0, 20, 20)
button.setTitle(title, forState: .Normal)
button.sizeToFit()
button.titleLabel.font = UIFont.systemFontOfSize(15)
button.setTranslatesAutoresizingMaskIntoConstraints(false)
button.backgroundColor = UIColor(white: 1.0, alpha: 1.0)
button.setTitleColor(UIColor.darkGrayColor(), forState: .Normal)
button.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
return button
}
|
In the code above, we are going to add a button programmatically and set its properties. We could have used nibs but with the sheer number of buttons we are going to have to manage, this is a better option.
The code will call a method called didTapButton when the button is tapped. So let’s add that method now.
1
2
3
4
5
6
7
8
| func didTapButton(sender: AnyObject?) {
let button = sender as UIButton
let title = button.titleForState(.Normal)
var proxy = textDocumentProxy as UITextDocumentProxy proxy.insertText(title)
}
|
In the method above, we implement the Swift way of writing a button event handler. AnyObject is like the id object in Objective-C. We cast the sender into a UIButton and then get the title of the button which in this case, will be the text we want to enter in the text field.
To enter text using the keyboard, we use the textDocumentProxy object and call the insertText method.
The next step is to add a button to our Keyboard view. Add the two lines of code below to the viewDidLoad method. You can remove the automatically generated code in the viewDidLoad and textDidChange methods.
1
2
3
4
5
6
7
| override func viewDidLoad() {
super.viewDidLoad()
let button = createButtonWithTitle("A")
self.view.addSubview(button)
}
|
This adds a button with the title “A” into the keyboard. This will be our A key.
Now if you run the app and tap in the text field, you should see the A key in the keyboard. (You probably have to switch the keyboard, like we did the last time).
Tap the A button and see what happens… We’ve got text!
Okay, lets add some more keys and make this bad boy look like a real keyboard.
Let’s change the code we added to the viewDidLoad method.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| override func viewDidLoad() {
super.viewDidLoad() let buttonTitles = ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"]
var buttons = UIButton[]() var keyboardRowView = UIView(frame: CGRectMake(0, 0, 320, 50))
for buttonTitle in buttonTitles{
let button = createButtonWithTitle(buttonTitle)
buttons.append(button)
keyboardRowView.addSubview(button)
}
self.view.addSubview(keyboardRowView)
}
|
Now with this new piece of code, we have created and array of button titles and we create a list of buttons from these. Each button is now added to an array as well as a UIView which will be our first row of keys. This view is then added to the main keyboard view.
If you run this, you will probably only see the P key since all the buttons are in the same location. We need to add some constraints programmatically so they can be aligned in a row.
So we will create a new method to create the constraints
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| func addIndividualButtonConstraints(buttons: UIButton[], mainView: UIView){
for (index, button) in enumerate(buttons) { var topConstraint = NSLayoutConstraint(item: button, attribute: .Top, relatedBy: .Equal, toItem: mainView, attribute: .Top, multiplier: 1.0, constant: 1) var bottomConstraint = NSLayoutConstraint(item: button, attribute: .Bottom, relatedBy: .Equal, toItem: mainView, attribute: .Bottom, multiplier: 1.0, constant: -1) var rightConstraint : NSLayoutConstraint!
if index == buttons.count - 1 { rightConstraint = NSLayoutConstraint(item: button, attribute: .Right, relatedBy: .Equal, toItem: mainView, attribute: .Right, multiplier: 1.0, constant: -1)
}else{ let nextButton = buttons[index+1] rightConstraint = NSLayoutConstraint(item: button, attribute: .Right, relatedBy: .Equal, toItem: nextButton, attribute: .Left, multiplier: 1.0, constant: -1)
} var leftConstraint : NSLayoutConstraint!
if index == 0 { leftConstraint = NSLayoutConstraint(item: button, attribute: .Left, relatedBy: .Equal, toItem: mainView, attribute: .Left, multiplier: 1.0, constant: 1)
}else{ let prevtButton = buttons[index-1] leftConstraint = NSLayoutConstraint(item: button, attribute: .Left, relatedBy: .Equal, toItem: prevtButton, attribute: .Right, multiplier: 1.0, constant: 1) let firstButton = buttons[0] var widthConstraint = NSLayoutConstraint(item: firstButton, attribute: .Width, relatedBy: .Equal, toItem: button, attribute: .Width, multiplier: 1.0, constant: 0)
mainView.addConstraint(widthConstraint)
}
mainView.addConstraints([topConstraint, bottomConstraint, rightConstraint, leftConstraint])
}
}
|
That is a lot of code, eh? With AutoLayout, you can’t really build up to it and you have to add all constraints at once or else it doesn’t work. The main work of the code above is to add a 1px constraint to the top and bottom of each key in relation to the view for the row. It also adds a 1px constraint to the left and right of each key in relation to the adjacent keys (or the row view if it is the first or last key in the row).
If you add the method call to the viewDidLoad function, we should be able to see the new row of keys show up.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| override func viewDidLoad() {
super.viewDidLoad() let buttonTitles = ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"]
var buttons = UIButton[]() var keyboardRowView = UIView(frame: CGRectMake(0, 0, 320, 50))
for buttonTitle in buttonTitles{
let button = createButtonWithTitle(buttonTitle)
buttons.append(button)
keyboardRowView.addSubview(button)
}
self.view.addSubview(keyboardRowView) addIndividualButtonConstraints(buttons, mainView: keyboardRowView)
}
|
Now, that’s looking more like a keyboard. The next step is to add the other row of keys. To do that, let’s do some quick refactoring. Create a new method that will implement each row of keys.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| func createRowOfButtons(buttonTitles: NSString[]) -> UIView {
var buttons = UIButton[]() var keyboardRowView = UIView(frame: CGRectMake(0, 0, 320, 50))
for buttonTitle in buttonTitles{
let button = createButtonWithTitle(buttonTitle)
buttons.append(button)
keyboardRowView.addSubview(button)
} addIndividualButtonConstraints(buttons, mainView: keyboardRowView)
return keyboardRowView }
|
We have basically extracted the viewDidLoad code into it’s own method. This new method takes in the array of titles for the row and returns the view that contains all the buttons for the row. Now we can call this bit of code for every row we want to add.
So our new vieDidLoad method will look like this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| override func viewDidLoad() {
super.viewDidLoad()
let buttonTitles1 = ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"]
let buttonTitles2 = ["A", "S", "D", "F", "G", "H", "J", "K", "L"]
let buttonTitles3 = ["CP", "Z", "X", "C", "V", "B", "N", "M", "BP"]
let buttonTitles4 = ["CHG", "SPACE", "RETURN"]
var row1 = createRowOfButtons(buttonTitles1)
var row2 = createRowOfButtons(buttonTitles2)
var row3 = createRowOfButtons(buttonTitles3)
var row4 = createRowOfButtons(buttonTitles4)
self.view.addSubview(row1)
self.view.addSubview(row2)
self.view.addSubview(row3)
self.view.addSubview(row4)
}
|
In the code above, we have added 4 rows of keys and then added these keys to a row which in turn is added to the main view.
We could run the code now, but you would only see the last row since they are all at the same location. We need to add some Autolayout constraints.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| func addConstraintsToInputView(inputView: UIView, rowViews: UIView[]){
for (index, rowView) in enumerate(rowViews) { var rightSideConstraint = NSLayoutConstraint(item: rowView, attribute: .Right, relatedBy: .Equal, toItem: inputView, attribute: .Right, multiplier: 1.0, constant: -1) var leftConstraint = NSLayoutConstraint(item: rowView, attribute: .Left, relatedBy: .Equal, toItem: inputView, attribute: .Left, multiplier: 1.0, constant: 1)
inputView.addConstraints([leftConstraint, rightSideConstraint]) var topConstraint: NSLayoutConstraint
if index == 0 { topConstraint = NSLayoutConstraint(item: rowView, attribute: .Top, relatedBy: .Equal, toItem: inputView, attribute: .Top, multiplier: 1.0, constant: 0)
}else{ let prevRow = rowViews[index-1] topConstraint = NSLayoutConstraint(item: rowView, attribute: .Top, relatedBy: .Equal, toItem: prevRow, attribute: .Bottom, multiplier: 1.0, constant: 0) let firstRow = rowViews[0] var heightConstraint = NSLayoutConstraint(item: firstRow, attribute: .Height, relatedBy: .Equal, toItem: rowView, attribute: .Height, multiplier: 1.0, constant: 0)
inputView.addConstraint(heightConstraint)
}
inputView.addConstraint(topConstraint) var bottomConstraint: NSLayoutConstraint
if index == rowViews.count - 1 { bottomConstraint = NSLayoutConstraint(item: rowView, attribute: .Bottom, relatedBy: .Equal, toItem: inputView, attribute: .Bottom, multiplier: 1.0, constant: 0)
}else{ let nextRow = rowViews[index+1] bottomConstraint = NSLayoutConstraint(item: rowView, attribute: .Bottom, relatedBy: .Equal, toItem: nextRow, attribute: .Top, multiplier: 1.0, constant: 0)
}
inputView.addConstraint(bottomConstraint)
}
}
|
This new method does a similar function to the last auto layout code we added. It adds a 1px constraint to the left and right of the row in relation to the main view and the adds a 0px constraint between the each row and the next one below and above it.
Now we need to call this bit of code from our viewDidLoad method.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| override func viewDidLoad() {
super.viewDidLoad()
let buttonTitles1 = ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"]
let buttonTitles2 = ["A", "S", "D", "F", "G", "H", "J", "K", "L"]
let buttonTitles3 = ["CP", "Z", "X", "C", "V", "B", "N", "M", "BP"]
let buttonTitles4 = ["CHG", "SPACE", "RETURN"]
var row1 = createRowOfButtons(buttonTitles1)
var row2 = createRowOfButtons(buttonTitles2)
var row3 = createRowOfButtons(buttonTitles3)
var row4 = createRowOfButtons(buttonTitles4)
self.view.addSubview(row1)
self.view.addSubview(row2)
self.view.addSubview(row3)
self.view.addSubview(row4)
row1.setTranslatesAutoresizingMaskIntoConstraints(false)
row2.setTranslatesAutoresizingMaskIntoConstraints(false)
row3.setTranslatesAutoresizingMaskIntoConstraints(false)
row4.setTranslatesAutoresizingMaskIntoConstraints(false)
addConstraintsToInputView(self.view, rowViews: [row1, row2, row3, row4])
}
|
Here’s our new viewDidLoad method. You will see that we set the TranslatesAutoresizingMaskIntoConstraints on each row to false. This is to make sure our views use the auto layout methods and not springs and struts for the layout.
Now if you run the app, you will see out keyboard all nicely laid out. You can tap all the buttons to see them entered into the text field.
There’s a small problem though, the non-text keys are not functioning correctly. (for example, the backspace and space keys).
To fix that, we will need to change our didTapButton method to add the correct methods to call for these keys.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| func didTapButton(sender: AnyObject?) {
let button = sender as UIButton
let title = button.titleForState(.Normal) as String
var proxy = textDocumentProxy as UITextDocumentProxy proxy.insertText(title)
switch title {
case "BP" :
proxy.deleteBackward()
case "RETURN" :
proxy.insertText("\n")
case "SPACE" :
proxy.insertText(" ")
case "CHG" :
self.advanceToNextInputMode()
default :
proxy.insertText(title)
}
}
|
We have included a switch statement which has a case statement for the title of the key. The backspace key calls the deleteBackward method on the proxy, the space key inserts a space and the CHG key changes the keyboard to either the system keyboard or the next keyboard installed.
What’s next?
We have come to the end of this tutorial on how to create a basic custom keyboard. Your homework is to take this further and see if you can add more advanced functionality, like using the Caps Lock to add uppercase letters, switching to a numeric/symbol keyboard scheme etc.
No comments:
Post a Comment