How to make a custom keyboard in iOS 8 using Swift

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 UIInputViewControllerthat controls the keyboard and responds to user events.

https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/ExtensibilityPG/Art/keyboard_architecture_2x.png

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
  • the dismissKeyboard method can surprisingly be called to dismiss the keyboard
  • advanceToNextInputMode 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 selectionWillChangeselectionDidChangetextWillChange 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.

add 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.

add a target

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 dashdelete 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 :)

 

  51 comments for “How to make a custom keyboard in iOS 8 using Swift

  1. June 27, 2014 at 6:06 pm
  2. mr_moneypants
    June 28, 2014 at 6:01 am

    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

    • June 28, 2014 at 11:25 am

      You can’t change the font of the input. You can only insert or delete text.

  3. June 29, 2014 at 1:13 pm

    Great article!
    Did you find a way to get ALL the inputted text from the text field?
    Thanks

  4. Jordan
    June 30, 2014 at 3:57 am

    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.

    • June 30, 2014 at 12:31 pm

      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!

      • Jordan
        July 1, 2014 at 2:37 am

        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!

        • July 1, 2014 at 3:04 am

          Oh, I found the problem! I was changing the class of the view, not the File Owner. Oops.

  5. Jordan
    July 1, 2014 at 4:33 am

    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.

    • July 1, 2014 at 10:33 am

      You should add constraints for you container view

      • Jordan
        July 2, 2014 at 5:13 am

        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.

  6. Jay Bush
    July 1, 2014 at 6:10 pm

    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.

  7. Forrest Maready
    July 3, 2014 at 7:06 pm

    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!

    • July 4, 2014 at 10:36 am

      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.

  8. Isuru
    July 5, 2014 at 11:55 am

    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.

  9. SwiftProgrammer
    July 16, 2014 at 3:34 am

    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.

  10. Mark
    July 16, 2014 at 4:21 pm

    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.

  11. July 23, 2014 at 1:31 am

    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!

    • July 23, 2014 at 2:02 am

      Nevermind… I got it to work

  12. August 26, 2014 at 3:07 pm

    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

  13. Klaus
    September 15, 2014 at 9:10 pm

    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?

    • Klaus
      September 15, 2014 at 9:12 pm

      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?

      • September 18, 2014 at 5:13 pm

        The principles shown in the tutorial work for both iPhone and iPad. The code works only on iPad.

        • Klaus
          September 23, 2014 at 7:44 pm

          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…

          • Klaus
            October 4, 2014 at 5:51 pm

            it now works for both, iPhone and iPad. It was an error in XCode Beta that is fixed with release 6(.0.1).

  14. Jawad Rehman
    September 24, 2014 at 9:08 pm

    Do I need to be on Yosemite to do this ? my Xcode doesn’t have the App Extension option.

    Thanks

    • September 25, 2014 at 11:22 am

      you need XCode 6

    • Klaus
      October 4, 2014 at 5:54 pm

      you need XCode 6. And be aware that it is slightly confusing. The extension is a target within an application, not an application itself.

  15. Kevin Rogers
    September 25, 2014 at 8:05 pm

    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…

    • September 25, 2014 at 10:41 pm

      You cannot do that. Emojis are unicode characters.

      • Kevin Rogers
        September 29, 2014 at 7:41 pm

        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…

  16. September 26, 2014 at 5:02 pm

    The keyboard crashes on Xcode 6.0.1 – I ran it straight from your downloadable source to double check.

  17. October 12, 2014 at 4:30 pm

    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

  18. Tom
    October 17, 2014 at 7:02 am

    2014-10-17 13:57:23.410 MorseCode[1194:35339] plugin weheartswift.com.MorseCode.MorseCodeKeyboard interrupted

    Failed at start. how can i solve this?

    • October 17, 2014 at 12:16 pm

      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.

  19. Mordechai Levi
    October 26, 2014 at 2:28 pm

    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.

    • October 26, 2014 at 9:48 pm

      Try opening Spotlight in the simulator.

      • Mordechai Levi
        October 27, 2014 at 3:35 am

        Did that doesn’t work, I tried a bunch of apps, and ran it on the device but couldn’t get it to work.

  20. January 31, 2015 at 12:36 pm

    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 :)

    • January 31, 2015 at 8:00 pm

      That’s awesome!! :)

      • Marcus
        October 26, 2015 at 8:58 am

        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!

        • October 26, 2015 at 12:14 pm

          You can implement each sheet as a view controller and then add all of them as child view controllers in the keyboard view controller.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe
We send about one email per week with our latest tutorials and updates
Never display this again :)