Custom In-App Keyboards

Recently, I was asked by the CEO of my project to build a custom numeric keyboard for the iOS app. It’s an iPad app, and as you might know, even though there is this “numeric keyboard” where you have the numeric keys above and the alphabet below, they wanted it to be completely numeric. Well, to be more precise, they wanted it to be a “phone keyboard”, complete with “+”, “(” and “)” symbols, a custom keyboard for entering phone numbers. Even though this might seem unnecessary, there are situations where it is actually useful. In this case, the reason was allowing non-technical people to enter phone numbers, distraction-free, avoiding the distraction or frustration of trying to enter symbols or alphabet letters.

Whatever the reason, ever since iOS 8 introduced custom keyboards and apps like SwiftKey started to appear, there is a lot of information out there on how to build custom keyboard extensions. However, we should really differentiate between these and In-App keyboards, because they cover two completely different scenarios.

In-App keyboards are those that are embedded and constrained to your application, but they are ready to use without any interaction other than opening the App. Custom iOS 8 keyboards extensions, on the other hand, work as general keyboards you can enable and use in the iOS device globally, but they are not well suited for scenarios like the one I described, because they need to be enabled from the Settings App in order to be available in your App, and of course you cannot restrict them to a limited set of fields.

In this post, we will be talking about this scenario, where you need to implement a custom keyboard, just for your App, that will appear only in some fields or text areas that you specify, and no external configuration is needed, they work right away.

Our application

 

We will be building a very simple application. It will have just two textfields, one of them a normal, text input UITextField, and the other one will be our custom phone textfield. Selecting the phone textfield will bring up a custom keyboard with just numbers, a backspace key (to allow the user to correct wrong input) and some relevant symbols (like “+”, “-“, parenthesis, etc…).

Our goal will be creating an interface to easily set any UITextField as a numeric phone text field, without requiring subclassing, at any moment in our view controllers by just calling a single method on an instance of UITextField.

The Basics

In order to set a custom input keyboard for a UITextField, we need to setup the inputView property of the text field. This view can be completely designed. It could contain buttons, images, other views, or anything we might want to include. It does not even need to look like a keyboard, so you could, for instance, design a custom view that allows you to draw the characters by swiping and drawing them on a view. Everything’s possible.

So all we have to do is define our custom View. Unfortunately, Xcode doesn’t provide with a visual/code combo like for UITableViewCell, so we need to create them separately and link them together.
First, we will create the Xib/Nib file. We’ll go to File > New File > User Interface > View. Then, we will add the buttons. It’s important to have in mind that this view will be displayed in all possible iOS devices, in all possible device orientations, so you must be very careful to set the AutoLayout constraints properly. The trick here is assuming that the frame will be wider than higher, and use relative proportions to the superview for the size of the buttons instead of fixed values. I.E: don’t set a width of 40 and a heigh of 60, instead, distribute a fixed number of buttons per row, say, 10, and then set the first button width to 1/11 or 1/12 of its superview’s width (to allow for span space between buttons), and an aspect ratio of 4/6. Set all other buttons to the same aspect ratio and same width as the first one.

We will set the tag for numeric buttons equal to the number the button represents. So the button “5” will have a tag of 5. That will help us later when calling our delegate and reacting to button interactions

Once we have that in place, we’ll take care of the class. We’ll add a new file by going to File > New File > Cocoa Touch Class. We’ll set the super class to “UIView”. Then, we need do link the NIB file with our class definition. We will do so in the IB of the NIB file, selecting the “File Owner” element in the view hierarchy at the left, and then in the Identity Inspector at the right, we will change the class to “Numeric Keyboard”. Now, we can start creating outlets for our view. We will create an outlet for every button.

We will also add three different IBActions for the buttons. One for all the numeric buttons (0 to 9), one for all the symbol buttons (parenthesis, plus sign, etc), and another one for the backspace button. We’ll call them “numericButtonPressed”, “symbolWasPressed” and “backspacePressed” respectively.

The first one will get the right number for the button tag, so you should set the IBAction sender to UIButton class for easy access to the tag property. The second one will get the symbol from the text (so it’s nice to have the sender specified as a UIButton instead of Any too), and the third one doesn’t need further description.

In order to complete the binding between the class and the view, we will initialise our class by reading the view visual hierarchy from the NIB file on init and adding it to our the main view:

class NumericKeyboard: UIView {
 // MARK: - outlets
 // numbers
 @IBOutlet weak var buttonKey0: UIButton!
 @IBOutlet weak var buttonKey1: UIButton!
 @IBOutlet weak var buttonKey2: UIButton!
 @IBOutlet weak var buttonKey3: UIButton!
 @IBOutlet weak var buttonKey4: UIButton!
 @IBOutlet weak var buttonKey5: UIButton!
 @IBOutlet weak var buttonKey6: UIButton!
 @IBOutlet weak var buttonKey7: UIButton!
 @IBOutlet weak var buttonKey8: UIButton!
 @IBOutlet weak var buttonKey9: UIButton!
 // backspace
 @IBOutlet weak var buttonKeyBackspace: UIButton!
 
 // symbols
 @IBOutlet weak var buttonKeyLeftParenthesis: UIButton!
 @IBOutlet weak var buttonKeyRightParenthesis: UIButton!
 @IBOutlet weak var buttonKeyPoint: UIButton!
 @IBOutlet weak var buttonKeyComma: UIButton!
 @IBOutlet weak var buttonKeyHyphen: UIButton!
 
 // MARK: - Initialization and lifecycle.
 
 override init(frame: CGRect) {
   super.init(frame: frame)
   initializeKeyboard()
 }
 
 required init?(coder aDecoder: NSCoder) {
   super.init(coder: aDecoder)
   initializeKeyboard()
 }
 
 func initializeKeyboard() {
   // set view
   let xibFileName = "NumericKeyboard"
   let view = Bundle.main.loadNibNamed(xibFileName, owner: self, options: nil)![0] as! UIView
   self.addSubview(view)
   view.frame = self.bounds
 }
}

Tweaking the buttons

As we are replacing the system keyboard, we need to design the experience of pushing those buttons as if the user was interacting with the system keyboard. iOS users have really high expectations when it comes to how their apps’ interfaces behave, so the more we mimic the keyboard experience, the better.

A good first step is defining two different states for our button: normal and pressed, and set two different images for our buttons in each of these states. If we set all the buttons to “System” type, UIKit will make sure that the button interaction behaves correctly, and our selected image is triggered when the user pushes the button.

We will build an array with all of our numeric and symbol buttons and create a method, that gets called on “initializeKeyboard”, to apply the same unselected/selected images to all of them. We will set the unselected/selected images for the backspace button independently, as this key appearance is different from the rest of the buttons.

class NumericKeyboard: UIView {
  // all non-backspace button outlets
  var allButtons: [UIButton] { return [buttonKey0, buttonKey1, buttonKey2, buttonKey3, buttonKey4, buttonKey5, buttonKey6, buttonKey7, buttonKey8, buttonKey9, buttonKeyLeftParenthesis, buttonKeyRightParenthesis, buttonKeyPoint, buttonKeyComma, buttonKeyHyphen] }

  func initializeKeyboard() {
    // set view
    let xibFileName = "NumericKeyboard"
    let view = Bundle.main.loadNibNamed(xibFileName, owner: self, options: nil)![0] 
      as! UIView
    self.addSubview(view)
    view.frame = self.bounds
 
    // set buttons appearance.
    updateButtonsAppearance()
  }

  // MARK: - Changes in appearance 
  fileprivate func updateButtonsAppearance() {
    for button in allButtons {
      button.setTitleColor(normalFontColor, for: .normal)
      button.setTitleColor(pressedFontColor, for: [.selected, .highlighted])
      button.setBackgroundImage(normalBackgroundImage, for: .normal)
      button.setBackgroundImage(pressedBackgroundImage, for: [.selected, .highlighted])
    }
  }
}

We will also add some useful properties for allowing the customisation of the keys and the font colours. These properties will call our updateButtonsAppearance method:

// private consts
private let kDLNumericKeyboardNormalImage = UIImage(named: "numericKeyBackground")!
private let kDLNumericKeyboardPressedImage = UIImage(named: "pressedNumericKeyBackground")!

// appearance variables
var normalBackgroundImage = kDLNumericKeyboardNormalImage { didSet { updateButtonsAppearance() } }
var pressedBackgroundImage = kDLNumericKeyboardPressedImage { didSet { updateButtonsAppearance() } }
var normalFontColor = UIColor.black { didSet { updateButtonsAppearance() } }
var pressedFontColor = UIColor.white { didSet { updateButtonsAppearance() } }

Of course there are lots of other things we might do to improve the appearance of our keyboard, or to make it more similar to the system keyboard and provide a more standard experience to the user, but this is a great start for our keyboard.

Reacting to our custom keys

Now it’s time to give some interactivity to our keyboard view. We will define a delegation protocol for our view called “NumericKeyboardDelegate”. This protocol will have three main methods, one for every type of key we are supporting: “numeric” keys, “symbol” keys and the backspace. We will call the delegate in the IBActions we defined earlier for the buttons:

@objc protocol NumericKeyboardDelegate {
 func numericKeyPressed(key: Int)
 func numericBackspacePressed()
 func numericSymbolPressed(symbol: String)
}

class NumericKeyboard: UIView {
  // MARK: - Button actions
  @IBAction func numericButtonPressed(_ sender: UIButton) {
    self.delegate?.numericKeyPressed(key: sender.tag)
  }

  @IBAction func backspacePressed(_ sender: AnyObject) {
    self.delegate?.numericBackspacePressed()
  }
 
  @IBAction func symbolWasPressed(_ sender: UIButton) {
    if let symbol = sender.titleLabel?.text, symbol.characters.count > 0 {
      self.delegate?.numericSymbolPressed(symbol: symbol)
    }
  }
 
}

Then, we will create a UITextField extension implementing that protocol to react to the different methods and updating the UITextfield text accordingly.:

extension UITextField: NumericKeyboardDelegate {
 // MARK: - NumericKeyboardDelegate methods
 
  internal func numericKeyPressed(key: Int) {
    self.text?.append("\(key)")
  }
 
  internal func numericBackspacePressed() {
    if var text = self.text, text.characters.count > 0 {
      _ = text.remove(at: text.index(before: text.endIndex))
      self.text = text
    }
  }
 
  internal func numericSymbolPressed(symbol: String) {
    self.text?.append(symbol)
  }
}

Now, all that’s left is creating the instance of our custom keyboard view, assigning it to the desired UITextField’s input view, and setting the delegates, but it would be nice if we could have a function that, just by calling a method on the textfield, would take care of everything, right? Those small details are the difference between a custom control that’s a pleasure to use and a useful-but-kind-of-ugly control, so let’s add some polish to our custom numeric keyboard.

Final details

We will modify our extension to UITextField to add a method called “setAsNumericKeyboard“, that will create the new view, set its size properly, set it as the input view of the textfield, and bridge the delegation, allowing us to specify an external delegate that will also receive the notifications for the different key button interactions. We will also add a method “unsetAsNumericKeyboard” to get the textfield back to a normal text input textfield.

extension UITextField: NumericKeyboardDelegate {
  // MARK: - Public methods to set or unset this uitextfield as NumericKeyboard.
 
  func setAsNumericKeyboard(delegate: NumericKeyboardDelegate?) {
    let numericKeyboard = NumericKeyboard(frame: CGRect(x: 0, y: 0, width: 0, height: kDLNumericKeyboardRecommendedHeight))
    self.inputView = numericKeyboard
    numericKeyboardDelegate = delegate
    numericKeyboard.delegate = self
  }
 
  func unsetAsNumericKeyboard() {
    if let numericKeyboard = self.inputView as? NumericKeyboard {
      numericKeyboard.delegate = nil
    }
    self.inputView = nil
    numericKeyboardDelegate = nil
  }
  ...

}

Now, it’s really easy to set any textfield as a phone keyboard input textfield by just calling a method in our UIViewController:

class ViewController: UIViewController, NumericKeyboardDelegate {
  // outlets
  @IBOutlet weak var customNumericTextfield: UITextField!
 
 override func viewWillAppear(_ animated: Bool) {
   super.viewWillAppear(animated)
   customNumericTextfield.setAsNumericKeyboard(delegate: self)
 }

 func numericKeyPressed(key: Int) {
   print("Numeric key \(key) pressed!")
 }
 
 func numericBackspacePressed() {
   print("Backspace pressed!")
 }
 
 func numericSymbolPressed(symbol: String) {
   print("Symbol \(symbol) pressed!")
 }
}

Bonus: Setting a completely custom appearance for buttons

Previously, we had defined the buttons as “System” type. This enforces a series of appearance rules and appearance change behaviors when the button is selected, highlighted, etc, that are common to all system buttons. If you want to apply a custom behavior to the button appearance changes, i.e: choosing the background and the color of the text when it’s on highlighted/selected status, you need to change the button type to “Custom”. Then, choose the proper color for the button for the selected/highlighted states and voila!

I added that behavior to the “0” button in the keyboard, so you can check if you rather have this completely custom behavior or the “system” one. The custom appearance sets the background to our dark gray background image, and the text color for the button to white. If you want the zero to behave like the rest of the buttons, just set its type to “System”. If you want the rest of the buttons to behave like the zero, simply set their type to “custom” and set the proper selected/highlighted colors for them. I agree with your comments that probably these settings should be presented in a more intuitive and centralized way for us developers to modify.

Where to go from here

In this tutorial, you learned how to deploy a custom In App keyboard to some of the fields of your App just for your application, without the user needing to add that custom keyboard anywhere in settings.

You can see the results in this animated gif. The result is custom InApp keyboard that’s ready to use right away, without the user needing to add it from settings or any other external app. The transition from one keyboard to the other is smooth and natural, and given that the input view and constraints have been properly designed, the keyboard will look nice in any iOS device, from an iPhone SE to a big iPad pro.

As always, you can download the full source project from my github repository.

Did you find it useful? Would you like to add something? Don’t hesitate to add a comment, and please check out the Digital Tips list if to be up to date of future posts and coding tips for iOS/swift developers every week.

 

 

  • Share:

Comments(18)

Carlos
February 21, 2017 At 2:31 pm

Hello, great tutorial thanks!!!

I have just one question, in the textfield extension you have:

numericKeyboardDelegate = delegate

where do you get the property numericKeyboardDelegate?

Is an objc_getAssociatedObject?

Thanks

    Ignacio Nieto Carvajal
    February 22, 2017 At 2:31 pm

    Hi there Carlos! Thanks for your kind words.

    This is a global property defined at UITextfield+NumericKeyboard.swift, that’s the extension to UITextField to easily and conveniently setup any UITextField as a numeric keyboard. Other (maybe more) elegant options are possible, of course.
    Hope this helps!

Brian Biswell
February 22, 2017 At 2:31 pm

The source code in the GitHub repository appears to only have the Xcode starter code, not the finished code.

    Ignacio Nieto Carvajal
    February 22, 2017 At 2:31 pm

    Sorry, my fault. I forgot to do the final push. You should be able to download it now, please let me know if it’s working ;)

      Brian Biswell
      February 23, 2017 At 2:31 pm

      Thank you, that provides the missing pieces – it now works and reproduces the demonstration in the animation.

      There’s one behavior that doesn’t seem to be working right, however. You define the alternate images and font color for the pressed state, but they do not seem to be being used. When you press one of the buttons they are highlighted, but the text remains white. When I added code to set the background images for the backspace key and I do not see the pressed image being used either. It appears there is some setting in IB that is overriding or preventing the behavior specified in updateButtonsAppearance()?

      Thanks for a great tutorial – this is just what I need for my own app.

        Ignacio Nieto Carvajal
        February 23, 2017 At 2:31 pm

        Hi Brian!
        Thanks, my pleasure to know you found it useful. That behavior you experience is due to the buttons being set to “system” type, which enforces a series of appearance restrictions. I added a bonus section “Bonus: Setting A Completely Custom Appearance For Buttons” so you can see how to completely set their appearance, including the background for the button and the text color. If you download the project again and click on the “zero” button, you’ll see that in action.

        Hope it helps!

          Brian Biswell
          February 24, 2017 At 2:31 pm

          I see now how you set the configuration for the 0 key in IB, and how you have to specify the values for all of the different button states. This works as I expected, but I was puzzled why the code in your updateButtonsAppearance() function was not affecting the button behavior.

          After some experimentation I found that it is, except for the two lines where you set the title color and background image for the .selected and .highlighted states. You call the two functions using the option set [.selected, .highlighted]. Although the documentation for UIControlState says it conforms to the OptionSet protocol (hence the code compiles), I don’t think setTitle() and setBackgroundImage() work properly with that input. If I break those statements into two function calls, one for .selected and one for .highlighted, now I can get the proper behavior by only setting the Custom style for the buttons in IB.

          Even for the System style the code in updateButtonsAppearance now works (I demonstrated this by setting the title colors to red and blue for the .normal and .selected states and seeing the colors change when I pressed the buttons).

          Ignacio Nieto Carvajal
          February 24, 2017 At 2:31 pm

          Hi there Brian, you are completely right. This is probably a bug in UIKit, and it’s not the first one that I know of or have found precisely regarding OptionSets. It seems that there’s some kind of misbehaving routine (probably because of the Objective-C/swift integration) that’s causing this.

          Thanks for letting us know! I will change the code to set those states separately. Thanks again.

Michael
June 21, 2017 At 2:31 pm

Great tutorial!! Is there any way to show this keyboard only when the device is an iPad and use the standard number pad on iPhone?

Thanks!

    Ignacio Nieto Carvajal
    June 21, 2017 At 2:31 pm

    Thanks, glad to know it helped. Of course, you just need to have a check when you are about to call “setAsNumericKeyboard” in your delegate, something like:


    switch UIDevice.current.userInterfaceIdiom {
    case .pad:
    myTextfield.setAsNumericKeyboard(delegate: self)
    default:
    // do something else
    }

Paul Addy
June 26, 2017 At 2:31 pm

Great tutorial! Than you so much!

    Ignacio Nieto Carvajal
    June 26, 2017 At 2:31 pm

    Hi there Paul, thanks, glad to know it helped. Sorry for not answering before.

Tzvia Shmuelevitch
July 13, 2017 At 2:31 pm

Hello Ignacio Nieto,
Thank you very much for this wonderful toturial and code!

One question:
In some cases I would like to allow entering only numeric characters and privent from enter any symbols.
When I am using a standard keyboard the following function invoke every time the user hit on the keyboard and privent him from enter non numeric character but when I changed to your keyboard the function doesn’t invoke. Any suggestions?

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let allowedCharacters = CharacterSet.decimalDigits
let characterSet = CharacterSet(charactersIn: string)
return allowedCharacters.isSuperset(of: characterSet)
}

Thanks,
Tzvia

    Ignacio Nieto Carvajal
    July 14, 2017 At 2:31 pm

    Dear Tzvia,

    Thanks for your words. Generally speaking, you shouldn’t block some characters that way. Think about it from your users perspective. I start typing on the keyboard, want to enter a concrete character, touch the key and… nothing… then I start frantically hitting that key, thinking that there’s a bug or my iPad just froze… That’s the perfect case for a custom in-app keyboard.

    As per your question… I don’t actually know. Depends on where you are putting that delegate method, and where you are assigning your delegate to the textfield.

      Tzvia Shmuelevitch
      July 14, 2017 At 2:31 pm

      Thanks

Alessandro
July 27, 2017 At 2:31 pm

Hello Ignacio,
I would like to show you my gratitude for your post: I really needed to implement a custom keyboard for a personal project I’m working on and I wasn’t able to go on before finding this awesome article… many thanks!!

The only difference I noticed in my code is on the view controller implementation: if I use “setAsNumericKeyboard” from there, protocol methods like “numericKeyPressed(key: Int)” inside the view controller are never called. While if I directly set the delegate and the input view in viewDidLoad:
let numericKeyboard = NumericKeyboard(…)
customTextView.inputView = numericKeyboard
numericKeyboard.delegate = self

then “numericKeyPressed(key: Int)” perfectly works! (maybe I made a mistake somewhere)
Again, a big thank for your helpful sharing :-)
Alessandro

    Ignacio Nieto Carvajal
    July 29, 2017 At 2:31 pm

    Hi there Alessandro,
    Thanks for your comment and your words. It’s weird because I haven’t experienced that behavior you are describing. I will anyway have a look at it because there might be a bug hidden somewhere. Thanks for letting me know.

Andrew
December 14, 2017 At 2:31 pm

Thank you very much for this tutorial. This is exactly what I was looking for.

Leave a Comment

sing in to post your comment or sign-up if you dont have any account.