tl;dr
I will go over the basics of a Keyboard Extension and then make a morse code keyboard using the new Application Extension API in iOS 8. It should take you about 20 minutes to go through all the steps. full code
Overview
A custom keyboard replaces the system keyboard for users who want capabilities such as a novel text input method or the ability to enter text in a language not otherwise supported in iOS. The essential function of a custom keyboard is simple: Respond to taps, gestures, or other input events and provide text, in the form of an unattributed NSString
object, at the text insertion point of the current text input object.
After a user chooses a keyboard it remains as the default one whenever a users opens an app. For this reason the keyboard mustallow the user to switch to another keyboard.
There are two development essentials for every custom keyboard:
Trust. Your custom keyboard gives you access to what a user types, so trust between you and your user is essential.
A “next keyboard” key. The affordance that lets a user switch to another keyboard is part of a keyboard’s user interface; you must provide one in your keyboard.
Note: If you only need to add a few buttons to the System Keyboard you should look into Custom Views for Data Input.
What a custom keyboard can’t do
There are certain text input objects that your custom keyboard is not eligible to type into: secure fields (aka. passwords), phone pad objects (like the phone number fields in Contacts).
Your custom keyboard does not have access to the view hierarchy of the input, it cannot control the cursor and select text.
Also the custom keyboard cannot display anything above the top row (like the system keyboard when you long press a key on the tow row).
Sandbox
By default, a keyboard has no network access and cannot share files with its containing app. To enable these things, set the value of the RequestsOpenAccess
Boolean key in the Info.plist
file to YES
. Doing this expands the keyboard’s sandbox, as described inEstablishing and Maintaining User Trust.
If you do request open access, your keyboard gains the following capabilities, each with a concomitant responsibility:
- Access to Location Services and the Address Book database, each requiring user permission on first access
- Option to use a shared container with the keyboard’s containing app, which enables features such as providing a custom lexicon management UI in the containing app
- Ability to send keystrokes and other input events for server-side processing
- Access to iCloud, which you can use, for example, to ensure that keyboard settings and your custom autocorrect lexicon are up to date on all devices owned by the user
- Access to Game Center and In-App Purchase, via the containing app
- Ability to work with managed apps, if you design your keyboard to support mobile device management (MDM)
Be sure to read Designing for User Trust, which describes your responsibilities for respecting and protecting user data in case you request open access.
High level view
The following figure shows some of the important objects in a running keyboard and shows where they come from in a typical development workflow. In the most basic form we have an app that contains the keyboard extension and a UIInputViewController
that controls the keyboard and responds to user events.
The Custom Keyboard template contains a subclass of UIInputViewController
, which is the primary view controller of your keyboard. Let’s look at the interface to get a feel of how it works:
class UIInputViewController : UIViewController, UITextInputDelegate, NSObjectProtocol {
var inputView: UIInputView!
var textDocumentProxy: NSObject! { get }
func dismissKeyboard()
func advanceToNextInputMode()
// This will not provide a complete repository of a language's vocabulary.
// It is solely intended to supplement existing lexicons.
func requestSupplementaryLexiconWithCompletion(completionHandler: ((UILexicon!) -> Void)!)
}
inputView
is the view used for the keyboard, it is the same as theview
property- the
dismissKeyboard
method can surprisingly be called to dismiss the keyboard advanceToNextInputMode
is used to change between keyboardstextDocumentProxy
is the object that you will use to interact with the current text input. For example:
self.textDocumentProxy.insertText("We ❤ Swift") // inserts the string "We ❤ Swift" at the insertion point
self.textDocumentProxy.deleteBackward() // Deletes the character to the left of the insertion point
UIInputViewController
conforms to theUITextInputDelegate
protocol which notifies you when the text or text selection changes using theselectionWillChange
,selectionDidChange
,textWillChange
andtextDidChange
events
Making a Morse Code keyboard
We will make a simple keyboard that can type dots and dashes, change the keyboard, delete a character and dismiss itself. This example uses only programmatically generated User Interface. We could have also used a Nib file for the interface – this will be covered at the end of the tutorial. Loading Nibs may negatively impact performance!
Create a new project
Open Xcode 6, create a new “Single Page Application” and select Swift as the programming language.
Add a text field
Open Main.storyboard
and drag a text field from the component library. We will use this to test the keyboard later.
Center the text field and add the necessary constraints.
Hint: If you call textField.becomeFirstResponder()
in viewDidLoad
the keyboard will open when you start the app.
Add the keyboard extension
Select the project file from the navigator and add a new target by pressing the +
button.
Select Application Extension
and use the Custom Keyboard
template. Name it MorseCodeKeyboard
.
This will create a new group named MorseCodeKeyboard
which contains two files KeyboardViewController.swift
and Info.plist
.
Cleaning up
Open KeyboardViewController.swift
. The template keyboard has one button created in order to switch between keyboards. Move the code from the viewDidLoad
method into a new method called addNextKeyboardButton
.
func addNextKeyboardButton() {
self.nextKeyboardButton = UIButton.buttonWithType(.System) as UIButton
...
var nextKeyboardButtonBottomConstraint = NSLayoutConstraint(item: self.nextKeyboardButton, attribute: .Bottom, relatedBy: .Equal, toItem: self.view, attribute: .Bottom, multiplier: 1.0, constant: -10.0)
self.view.addConstraints([nextKeyboardButtonLeftSideConstraint, nextKeyboardButtonBottomConstraint])
}
Create a method named addKeyboardButtons
and call it in viewDidLoad
. This will help organize the code, now we have just a few buttons but in a real project there will be much more. Call the addNextKeyboardButton
in addKeyboardButtons
.
class KeyboardViewController: UIInputViewController {
...
override func viewDidLoad() {
super.viewDidLoad()
addKeyboardButtons()
}
func addKeyboardButtons() {
addNextKeyboardButton()
}
...
}
Dot
Now lets add the dot button. Create a property named dotButton
of type UIButton!
.
class KeyboardViewController: UIInputViewController {
var nextKeyboardButton: UIButton!
var dotButton: UIButton!
...
}
Add a method called addDot
. Initialize the dotButton
property with a system button. Add a callback for the TouchUpInside
event. Set a bigger font and add rounded corners. Add constraints to position it 50
points to the left of the horizontal center and on the vertical center. The code is similar to the one of the nextKeyboardButton
.
func addDot() {
// initialize the button
dotButton = UIButton.buttonWithType(.System) as UIButton
dotButton.setTitle(".", forState: .Normal)
dotButton.sizeToFit()
dotButton.setTranslatesAutoresizingMaskIntoConstraints(false)
// adding a callback
dotButton.addTarget(self, action: "didTapDot", forControlEvents: .TouchUpInside)
// make the font bigger
dotButton.titleLabel.font = UIFont.systemFontOfSize(32)
// add rounded corners
dotButton.backgroundColor = UIColor(white: 0.9, alpha: 1)
dotButton.layer.cornerRadius = 5
view.addSubview(dotButton)
// makes the vertical centers equa;
var dotCenterYConstraint = NSLayoutConstraint(item: dotButton, attribute: .CenterY, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1.0, constant: 0)
// set the button 50 points to the left (-) of the horizontal center
var dotCenterXConstraint = NSLayoutConstraint(item: dotButton, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1.0, constant: -50)
view.addConstraints([dotCenterXConstraint, dotCenterYConstraint])
}
Implement the dotButton
callback by using the textDocumentProxy
.
func didTapDot() {
var proxy = textDocumentProxy as UITextDocumentProxy
proxy.insertText(".")
}
Call addDot
in addKeyboardButtons
.
func addKeyboardButtons() {
addDot()
addNextKeyboardButton()
}
The process is similar for dash
, delete
and hideKeyboard
. The deleteButton
will use the deleteBackward
method from the proxy, and the hideKeyboardButton
will use the dismissKeyboard
from the KeyboardViewController
.
Dash
The dash code is almost the same as the dotButton
code. To put the dashButton
symmetrical to the horizontal center just change the sign of the constant in the horizontal constraint.
func addDash() {
...
// set the button 50 points to the left (-) of the horizontal center
var dotCenterXConstraint = NSLayoutConstraint(item: dotButton, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1.0, constant: -50)
view.addConstraints([dashCenterXConstraint, dashCenterYConstraint])
}
func didTapDash() {
var proxy = textDocumentProxy as UITextDocumentProxy
proxy.insertText("_")
}
Delete
The delete button will use the deleteBackward
from the textDocumentProxy
to delete a character when pressed. The layout constraints are symmetrical to the nextKeyboardButton
( .Left
-> .Right
, .Bottom
-> .Top
).
func addDelete() {
deleteButton = UIButton.buttonWithType(.System) as UIButton
deleteButton.setTitle(" Delete ", forState: .Normal)
deleteButton.sizeToFit()
deleteButton.setTranslatesAutoresizingMaskIntoConstraints(false)
deleteButton.addTarget(self, action: "didTapDelete", forControlEvents: .TouchUpInside)
deleteButton.backgroundColor = UIColor(white: 0.9, alpha: 1)
deleteButton.layer.cornerRadius = 5
view.addSubview(deleteButton)
var rightSideConstraint = NSLayoutConstraint(item: deleteButton, attribute: .Right, relatedBy: .Equal, toItem: view, attribute: .Right, multiplier: 1.0, constant: -10.0)
var topConstraint = NSLayoutConstraint(item: deleteButton, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1.0, constant: +10.0)
view.addConstraints([rightSideConstraint, topConstraint])
}
func didTapDelete() {
var proxy = textDocumentProxy as UITextDocumentProxy
proxy.deleteBackward()
}
Hide keyboard
The hideKeyboardButton
will call dismissKeyboard
on the KeyboardViewController
when pressed.
func addHideKeyboardButton() {
hideKeyboardButton = UIButton.buttonWithType(.System) as UIButton
hideKeyboardButton.setTitle("Hide Keyboard", forState: .Normal)
hideKeyboardButton.sizeToFit()
hideKeyboardButton.setTranslatesAutoresizingMaskIntoConstraints(false)
hideKeyboardButton.addTarget(self, action: "dismissKeyboard", forControlEvents: .TouchUpInside)
view.addSubview(hideKeyboardButton)
var rightSideConstraint = NSLayoutConstraint(item: hideKeyboardButton, attribute: .Right, relatedBy: .Equal, toItem: view, attribute: .Right, multiplier: 1.0, constant: -10.0)
var bottomConstraint = NSLayoutConstraint(item: hideKeyboardButton, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1.0, constant: -10.0)
view.addConstraints([rightSideConstraint, bottomConstraint])
}
Using Nib files
In case writing constraints by hand is not your thing you can create an interface file and add it to your inputView
.
Create a Interface File
Right click the MorseCodeKeyboard
group and select new file.
Select User Interface and the View Template. Name the file CustomKeyboardInterface
.
Select File's Owner
and change the class name to KeyboardViewController
in the Identity Inspector
tab.
Add a button in the view and set the title to We ❤ Swift
. The interface should look like this:
Load the interface
In the init(nibName, bundle)
constructor load the CustomKeyboard
nib and save a reference to the custom interface
class KeyboardViewController: UIInputViewController {
...
var customInterface: UIView!
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
var nib = UINib(nibName: "CustomKeyBoardInterface", bundle: nil)
let objects = nib.instantiateWithOwner(self, options: nil)
customInterface = objects[0] as UIView
}
...
}
Add it to the inputView
In the viewDidLoad
method add the custom interface to the inputView.
class KeyboardViewController: UIInputViewController {
...
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(customInterface)
...
}
...
}
Add a callback for the button
class KeyboardViewController: UIInputViewController {
...
@IBAction func didTapWeheartSwift() {
var proxy = textDocumentProxy as UITextDocumentProxy
proxy.insertText("We ❤ Swift")
}
...
}
Connect the button event to the callback
Right click on the button and then click drag the touchUpInside
event to the didTapWeHeartSwift
IBAction
.
In the end the code should look like this.
Install the container app on your device
Build and run the project on your device. This will add a new keyboard on your device, but before you can use it you need to install it first.
Go to Settings
> General
. Select Keyboard
– it’s at the bottom of the list of options.
Select Keyboards
.
Select Add new Keyboard
. There you will find the MorseCode
keyboard in the purchased keyboard list. Select it and install the keyboard.
Now we can run the app again and enjoy our new keyboard.
Challenges
- read the App Extension Programming Guide from Apple
- make a keyboard with smileys for you favorite IM app
- coding keyboard
- make the morse code keyboard translate the input to letters and numbers
- a keyboard that has a Calculator App
– tutorial
If you found this useful please take a moment and share it with your friends
You can discuss this on HN https://news.ycombinator.com/item?id=7955362
Looks like an awesome tutorial. Thanks, I can’t wait to try it out.
One question though: I want to design a keyboard that outputs a custom font (I have designed my own font which uses non-alphanumeric characters).
I would prefer users to be able to use my custom keyboard to type this font without having to open an external app. Is this possible? Do I have to submit a new language to Apple? Any help would be appreciated!
Thanks
You can’t change the font of the input. You can only insert or delete text.
Great article!
Did you find a way to get ALL the inputted text from the text field?
Thanks
I don’t think you have access to all the text. But you could obtain the textual context near the insertion point from the documentContextBeforeInput property of the textDocumentProxyproperty https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UITextDocumentProxy_Protocol/index.html#//apple_ref/occ/intfp/UITextDocumentProxy/documentContextBeforeInput
Thanks for this tutorial! It’s great. I have been having a lot of trouble implementing a nib though. Upon tapping the text field, the app doesn’t crash but it experiences a SIGABORT. swift_reportUnimplementedInitializer – I’m running the app on my iPhone. If I comment out the line where objects var gets defined, it does not experience the issue. How do I fix this issue? It may be I have the incorrect nibName. Is it supposed to be the exact same as the xib file minus “.xib”? In the tut you put “CustomKeyBoard” but you said to call the xib file “CustomKeyboardInterface” Any help is much appreciated. Thanks again for the awesome tut.
There was a mistake in the tutorial. I updated it. Thanks!
The nibName is the same as the files name without the extension.
You can fix that error by making you customInterface an implicitly unwrapped optional
var customInterface: UIView!
Thanks Andrei. Unfortunately I already had the ! and I still get the error. I’ve even started over from scratch with a new target, only added the nib and code to initialize it, and I still get: Thread 1: EXC_BREAKPOINT (code=EXC_ARM_BREAKPOINT, subcode=0xe7ffdefe) and the line above it reads ; swift_reportUnimplementedInitializer. I’m running beta 2 of iOS 8 and Xcode. Do you know the cause of the error? Thanks!
Oh, I found the problem! I was changing the class of the view, not the File Owner. Oops.
Quick question. I’m laying out my buttons in the view using auto layout in the xib file and it looks great but when I run the app I cannot see the buttons I’ve aligned at the bottom nor the button at the top right. The button I have perfectly centered in the view is barely visible at the very bottom right. It seems the view is twice as large as it should it in both directions. Did you run into the same problem? I have changed the simulated metrics to freeform and changed the size to 320 by 216 for testing in portrait.
You should add constraints for you container view
Sorry, I’m not sure what you mean. IB won’t allow adding constraints to the container view itself, all options are disabled. If you mean the buttons should be related to the superview, that’s how they’re set up. For example the top right button is top space to superview, trailing space to superview.
Thanks for the tutorial Andrei. I’m currently trying to create a five row keyboard. Is there any way to increase the default height, or are we stuck with working on the space provided.
I don’t see a way.
Thanks for the tutorial Andrei. I’m still trying to wrap my head around when and where Implicitly Unwrapped Optionals (IUO) are needed. In your KeyboardViewController you initialize all everything with IUOs. If you remove any of the !’s, you get a ‘Property not initialized at super.init call”.
Can you explain why using a traditional variable causes this error and why an IUO doesn’t?
Many thanks!
I’m not 100% sure if this is the right way to do it (still learning). The compiler will throw an exception because variables that are not optional cannot be nil an must be initialised. Using IUO felt natural in this case.
This is something I’ve been looking forward to try out too. I read Apple’s documentation but it was a little too overwhelming for me. This tutorial looks like a good start. Thank you.
Looks good, gonna practise later. One thing I find it weird is that there is no interface builder template for custom keyboard. Otherwise it would be awesome.
I don’t understand how you are getting views to work when loading from a XIB file. I have spent days on this and there seems to be no way to get view to work properly from XIB files.
Right now when following your tutorial it automatically adds the view from the loaded XIB WITHOUT me explicitly adding it to the view hierarchy.
Hi:
I’ve followed your tutorial (except for the nib part) and installed it on the iOS simulator (xcode 6 b4), but I cannot use the keyboard.
Here is my code: http://pastebin.com/1EMGf5QZ. Is there something I missed?
Thanks!
Nevermind… I got it to work
I have defined a custom keyboard, all is well.
The issue I am now facing is setting keyboard type sensitivity, meaning, opening a mail related keyboard when entering a mail field, similarly internet/search keyboard types and so on.
Does anyone here have any experience with setting keyboard type trait?
(setKeyboardType:UIKeyboardTypeEmailAddress];)
Thanks,
Ami
I have trouble with the different ios device sizes. Your tutorial works fine for the iPad, but not for an iPhone. Do you have any idea why that could be?
sorry, should have been more specific.
On an iPad, no problem. But on an iPhone device it only shows the “next keyboard”. I even tried to make changes to the background colour, but those are not used, only on the iPad. I have to assume that the tutorial code only works on iPad. But that doesn’t make sense. Do I have to provide extra code for iPhones?
The principles shown in the tutorial work for both iPhone and iPad. The code works only on iPad.
Could you please describe what is iPad specific? I am struggling to see what I am doing wrong. It simply doesn’t show on the iPhone simulator. I only get the “Next Keyboard”, but nothing else. And my code is not executed at all…
it now works for both, iPhone and iPad. It was an error in XCode Beta that is fixed with release 6(.0.1).
Do I need to be on Yosemite to do this ? my Xcode doesn’t have the App Extension option.
Thanks
you need XCode 6
you need XCode 6. And be aware that it is slightly confusing. The extension is a target within an application, not an application itself.
Is it possible to define images as input instead of ACSII characters? I want to build a keyboard that allows the input of images selected in the container app, but I don’t know how that would fit in this model…
You cannot do that. Emojis are unicode characters.
I know that, but I don’t want to use Emojis. What about the GIF keyboard that’s popular right now? How does that work? That stores an image SOMEWHERE and puts it on the keyboard display, so… It’s possible…
https://github.com/ryanseys/keyboard-cat
The keyboard crashes on Xcode 6.0.1 – I ran it straight from your downloadable source to double check.
Hi Andrei Puni,
Thanks a lot for this great tutorial. I have been successful implementing my keyboard using your code. However, I have 2 questions and appreciate your help here:
1. when switching to my keyboard first from the keyboard by Apple (by tapping the globe button), I have to wait like 2/3 seconds. is it because too many uibutton as I implemented a to z.
2. How to set the size of the button instead of using sizeToFit() ? I used the following but the button went to upper left hand corner:
let image = UIImage(named: “globe”) as UIImage
let button = UIButton.buttonWithType(UIButtonType.System) as UIButton
button.frame = CGRectMake(0, 0, 50, 50)
button.setImage(image, forState: .Normal)
I just want to set the size and have the globe button (Next Keyboard) at position as usual, which is right beside the “123” button
Cheerios,
Mark
2014-10-17 13:57:23.410 MorseCode[1194:35339] plugin weheartswift.com.MorseCode.MorseCodeKeyboard interrupted
Failed at start. how can i solve this?
It does not work on some versions of Xcode. I’m using 6.0.1 and it works only on my device. In earlier versions it worked in the simulator as well.
Hi, I’ve been trying to run the keyboard in the simulator and on my device and it doesn’t work. I tried both the keyboard I created and the one from Github from this tutorial. I think this is a problem in Xcode 6.1. Do you know if they changed anything that we have to do to make it work? Thanks.
Try opening Spotlight in the simulator.
Did that doesn’t work, I tried a bunch of apps, and ran it on the device but couldn’t get it to work.
me neither..
A keyboard app made after reading this article
ImgKey turns your typing into image.
https://itunes.apple.com/us/app/imgkey/id957794970?ls=1&mt=8
ImgKey reduce the steps for searching images, so as to speed up your typing. It also make your text become more interesting and attractive!
Try it for FREE now
That’s awesome!!
Hello! I would like to create a custom keyboard for my application, but I still don’t find any tutorial about creation keyboard with multiple “sheets”. It is something like numbers and symbols in apple keyboard. You have the main keyboard and two extra “sheets”, or keyboard pages. Is there any possibility to create such one?
Thank you!
You can implement each sheet as a view controller and then add all of them as child view controllers in the keyboard view controller.