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.