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 the view property

is the view used for the keyboard, it is the same as the property the dismissKeyboard method can surprisingly be called to dismiss the keyboard

method can surprisingly be called to dismiss the keyboard advanceToNextInputMode is used to change between keyboards

is used to change between keyboards textDocumentProxy 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 the UITextInputDelegate protocol which notifies you when the text or text selection changes using the selectionWillChange , selectionDidChange , textWillChange and textDidChange 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