How to Make Custom Keyboard Only for My App in Swift

How to make custom keyboard only for my app in Swift?

The UITextField and UITextView classes that you use for text input both have an inputView property. This property is nil by default, but if you provide it with a view, that view will pop up instead of the normal keyboard when editing that text field.

To implement this, create a subclass of UIView that sets up your custom keyboard layout. Then, when you use a text field or text view in your app, you have to set it separately for every field, as follows:

let myCustomKeyboard = MyCustomKeyboardView()
myTextField.inputView = myCustomKeyboard

That's it, now your text field uses the custom keyboard you provided.

Since you want to use the keyboard globally in your app, and the keyboard has to be set separately for every text field or text view, this solution would lead to a lot of code duplication. Since code duplication is widely regarded as a bad thing to have, in your case a better implementation would be to subclass UITextField or UITextView (depending on which one you use, obviously), and set the keyboard in the constructor:

class MyCustomKeyboardTextField: UITextField {

override init() {
super.init()
setupCustomKeyboard()
}

required init(coder: aCoder) {
super.init(aCoder)
}

override func awakeFromNib() {
setupCustomKeyboard()
}

func setupCustomKeyboard() {
// Set the custom keyboard
self.inputView = MyCustomKeyboardView()
}
}

Now the custom keyboard view will be used wherever you use this subclass.

Custom Keyboard on iOS

For a custom keyboard that is specific to a single app, create a view and assign the view to UITextField.inputView:

textField.inputView = YourCustomKeyboard()

In my search I didn't find a way to tack on additional keys but there are examples of custom keyboards using inputView that are easy to adapt.

A custom decimal keyboard example

My hexadecimal keyboard version

Is it possible to create a custom keyboard with Swift without forcing the user to attach it through Setting screen?

In your own app

Yeah, sure. Just create a view and use that instead of calling the keyboard. You can probably override inputView on text inputs to automagically make your keyboard appear.

https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UITextField_Class/index.html

In other apps in iOS

Not a chance. Unless you do it for jailbroken devices only (don't do it for jailbroken devices).

Is a completely custom keyboard, for my app only, acceptable?

Totally acceptable inside your app! and there should be no acceptance criteria for that!

You can even make a custom keyboard to use outside your app (I developed two myself), however there are acceptance criteria for that (like having a button to switch to another keyboard).

Hope that helps!

How to create a custom iOS keyboard

I have solved my problem. I discovered this site

on StackOverflow that completely solved my issues. I was able to add customized keys to the keyboard, which was my primary issue.

I created a class called DigitButton.swift.

import UIKit

class DigitButton: UIButton {
var digit: Int = 0
}

class NumericKeyboard: UIView {
weak var target: (UIKeyInput & UITextInput)?
var useDecimalSeparator: Bool

lazy var parenthesis1: UIButton = {
let button = UIButton(type: .system)
let decimalSeparator = "("
button.setTitle(decimalSeparator, for: .normal)
button.titleLabel?.font = .preferredFont(forTextStyle: .largeTitle)
button.setTitleColor(.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.darkGray.cgColor
button.accessibilityTraits = [.keyboardKey]
button.accessibilityLabel = decimalSeparator
button.addTarget(self, action: #selector(didTapParenthesis1(_:)), for: .touchUpInside)
return button
}()

lazy var squareroot: UIButton = {
let button = UIButton(type: .system)
let decimalSeparator = "√"
button.setTitle(decimalSeparator, for: .normal)
button.titleLabel?.font = .preferredFont(forTextStyle: .largeTitle)
button.setTitleColor(.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.darkGray.cgColor
button.accessibilityTraits = [.keyboardKey]
button.accessibilityLabel = decimalSeparator
button.addTarget(self, action: #selector(didTapSquareRoot(_:)), for: .touchUpInside)
return button
}()

lazy var parenthesis2: UIButton = {
let button = UIButton(type: .system)
let decimalSeparator = ")"
button.setTitle(decimalSeparator, for: .normal)
button.titleLabel?.font = .preferredFont(forTextStyle: .largeTitle)
button.setTitleColor(.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.darkGray.cgColor
button.accessibilityTraits = [.keyboardKey]
button.accessibilityLabel = decimalSeparator
button.addTarget(self, action: #selector(didTapParenthesis2), for: .touchUpInside)
return button
}()

lazy var exponentButton: UIButton = {
let button = UIButton(type: .system)
let decimalSeparator = "^0"
button.setTitle(decimalSeparator, for: .normal)
button.titleLabel?.font = .preferredFont(forTextStyle: .largeTitle)
button.setTitleColor(.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.darkGray.cgColor
button.accessibilityTraits = [.keyboardKey]
button.accessibilityLabel = decimalSeparator
button.addTarget(self, action: #selector(didTapExponentButton(_:)), for: .touchUpInside)
return button
}()

lazy var exponentButton2: UIButton = {
let button = UIButton(type: .system)
let decimalSeparator = "^2"
button.setTitle(decimalSeparator, for: .normal)
button.titleLabel?.font = .preferredFont(forTextStyle: .largeTitle)
button.setTitleColor(.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.darkGray.cgColor
button.accessibilityTraits = [.keyboardKey]
button.accessibilityLabel = decimalSeparator
button.addTarget(self, action: #selector(didTapExponentButton2(_:)), for: .touchUpInside)
return button
}()

lazy var exponentButton3: UIButton = {
let button = UIButton(type: .system)
let decimalSeparator = "^3"
button.setTitle(decimalSeparator, for: .normal)
button.titleLabel?.font = .preferredFont(forTextStyle: .largeTitle)
button.setTitleColor(.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.darkGray.cgColor
button.accessibilityTraits = [.keyboardKey]
button.accessibilityLabel = decimalSeparator
button.addTarget(self, action: #selector(didTapExponentButton3(_:)), for: .touchUpInside)
return button
}()

lazy var exponentButton4: UIButton = {
let button = UIButton(type: .system)
let decimalSeparator = "^"
button.setTitle(decimalSeparator, for: .normal)
button.titleLabel?.font = .preferredFont(forTextStyle: .largeTitle)
button.setTitleColor(.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.darkGray.cgColor
button.accessibilityTraits = [.keyboardKey]
button.accessibilityLabel = decimalSeparator
button.addTarget(self, action: #selector(didTapExponentButton4(_:)), for: .touchUpInside)
return button
}()

lazy var addButton: UIButton = {
let button = UIButton(type: .system)
let decimalSeparator = "+"
button.setTitle(decimalSeparator, for: .normal)
button.titleLabel?.font = .preferredFont(forTextStyle: .largeTitle)
button.setTitleColor(.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.darkGray.cgColor
button.accessibilityTraits = [.keyboardKey]
button.accessibilityLabel = decimalSeparator
button.addTarget(self, action: #selector(didTapAddButton(_:)), for: .touchUpInside)
return button
}()

lazy var subtractButton: UIButton = {
let button = UIButton(type: .system)
let decimalSeparator = "-"
button.setTitle(decimalSeparator, for: .normal)
button.titleLabel?.font = .preferredFont(forTextStyle: .largeTitle)
button.setTitleColor(.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.darkGray.cgColor
button.accessibilityTraits = [.keyboardKey]
button.accessibilityLabel = decimalSeparator
button.addTarget(self, action: #selector(didTapSubtractButton(_:)), for: .touchUpInside)
return button
}()

lazy var divideButton: UIButton = {
let button = UIButton(type: .system)
let decimalSeparator = "/"
button.setTitle(decimalSeparator, for: .normal)
button.titleLabel?.font = .preferredFont(forTextStyle: .largeTitle)
button.setTitleColor(.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.darkGray.cgColor
button.accessibilityTraits = [.keyboardKey]
button.accessibilityLabel = decimalSeparator
button.addTarget(self, action: #selector(didTapDivideButton(_:)), for: .touchUpInside)
return button
}()


lazy var multiplyButton: UIButton = {
let button = UIButton(type: .system)
let decimalSeparator = "*"
button.setTitle(decimalSeparator, for: .normal)
button.titleLabel?.font = .preferredFont(forTextStyle: .largeTitle)
button.setTitleColor(.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.darkGray.cgColor
button.accessibilityTraits = [.keyboardKey]
button.accessibilityLabel = decimalSeparator
button.addTarget(self, action: #selector(didTapMultiplyButton(_:)), for: .touchUpInside)
return button
}()



lazy var numericButtons: [DigitButton] = (0...9).map {
let button = DigitButton(type: .system)
button.digit = $0
button.setTitle("\($0)", for: .normal)
button.titleLabel?.font = .preferredFont(forTextStyle: .title1)
button.setTitleColor(.black, for: .normal)
button.layer.borderWidth = 0.5
button.sizeToFit()
button.titleLabel?.numberOfLines = 1
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.lineBreakMode = .byTruncatingTail
button.layer.borderColor = UIColor.darkGray.cgColor
button.accessibilityTraits = [.keyboardKey]
button.addTarget(self, action: #selector(didTapDigitButton(_:)), for: .touchUpInside)
button.inputView.self?.sizeToFit()
return button
}

var deleteButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("⌫", for: .normal)
button.titleLabel?.font = .preferredFont(forTextStyle: .largeTitle)

button.setTitleColor(.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.darkGray.cgColor
button.accessibilityTraits = [.keyboardKey]
button.accessibilityLabel = "Delete"
button.addTarget(self, action: #selector(didTapDeleteButton(_:)), for: .touchUpInside)
return button
}()



init(target: UIKeyInput & UITextInput, useDecimalSeparator: Bool = false) {
self.target = target
self.useDecimalSeparator = useDecimalSeparator
super.init(frame: .zero)
configure()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

// MARK: - Actions

extension NumericKeyboard {

@objc func didTapSquareRoot(_ sender: DigitButton) {
insertText("√")
}

@objc func didTapParenthesis1(_ sender: DigitButton) {
insertText("(")
}

@objc func didTapParenthesis2(_ sender: DigitButton) {
insertText(")")
}
@objc func didTapDigitButton(_ sender: DigitButton) {
insertText("\(sender.digit)")
}

@objc func didTapDecimalButton(_ sender: DigitButton) {
insertText(Locale.current.decimalSeparator ?? ".")
}

@objc func didTapExponentButton(_ sender: DigitButton){
insertText("^0")
}

@objc func didTapExponentButton2(_ sender: DigitButton){
insertText("^2")
}

@objc func didTapExponentButton3(_ sender: DigitButton){
insertText("^3")
}

@objc func didTapExponentButton4(_ sender: DigitButton){
insertText("^")
}


@objc func didTapAddButton(_ sender: DigitButton){
insertText("+")
}


@objc func didTapSubtractButton(_ sender: DigitButton){
insertText("-")
}


@objc func didTapDivideButton(_ sender: DigitButton){
insertText("/")
}


@objc func didTapMultiplyButton(_ sender: DigitButton){
insertText("*")
}

@objc func didTapDeleteButton(_ sender: DigitButton) {
target?.deleteBackward()
}
}

// MARK: - Private initial configuration methods

private extension NumericKeyboard {
func configure() {
autoresizingMask = [.flexibleWidth, .flexibleHeight]
addButtons()
}

func addButtons() {
let stackView = createStackView(axis: .vertical)
stackView.frame = bounds
stackView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(stackView)

for row in 0 ..< 3 {
let subStackView = createStackView(axis: .horizontal)
stackView.addArrangedSubview(subStackView)

for column in 0 ..< 3 {
subStackView.addArrangedSubview(numericButtons[row * 3 + column + 1])
}
}

let subStackView = createStackView(axis: .horizontal)
stackView.addArrangedSubview(subStackView)

subStackView.addArrangedSubview(numericButtons[0])

subStackView.addArrangedSubview(parenthesis1)
subStackView.addArrangedSubview(parenthesis2)
subStackView.addArrangedSubview(squareroot)

subStackView.addArrangedSubview(addButton)
subStackView.addArrangedSubview(subtractButton)
subStackView.addArrangedSubview(multiplyButton)
subStackView.addArrangedSubview(divideButton)


subStackView.addArrangedSubview(exponentButton)
subStackView.addArrangedSubview(exponentButton2)
subStackView.addArrangedSubview(exponentButton4)

subStackView.addArrangedSubview(deleteButton)

}

func createStackView(axis: NSLayoutConstraint.Axis) -> UIStackView {
let stackView = UIStackView()
stackView.axis = axis
stackView.alignment = .fill
stackView.distribution = .fillProportionally
return stackView
}

func insertText(_ string: String) {
guard let range = target?.selectedRange else { return }

if let textField = target as? UITextField, textField.delegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) == false {
return
}

if let textView = target as? UITextView, textView.delegate?.textView?(textView, shouldChangeTextIn: range, replacementText: string) == false {
return
}

target?.insertText(string)
}
}

// MARK: - UITextInput extension

extension UITextInput {
var selectedRange: NSRange? {
guard let textRange = selectedTextRange else { return nil }

let location = offset(from: beginningOfDocument, to: textRange.start)
let length = offset(from: textRange.start, to: textRange.end)
return NSRange(location: location, length: length)
}
}

Then I set my InputView input method with textField.inputView = NumericKeyboard(target: textField)

This has worked perfectly.

A Swift example of Custom Views for Data Input (custom in-app keyboard)

The key is to use the existing UIKeyInput protocol, to which UITextField already conforms. Then your keyboard view need only to send insertText() and deleteBackward() to the control.

The following example creates a custom numeric keyboard:

class DigitButton: UIButton {
var digit: Int = 0
}

class NumericKeyboard: UIView {
weak var target: (UIKeyInput & UITextInput)?
var useDecimalSeparator: Bool

var numericButtons: [DigitButton] = (0...9).map {
let button = DigitButton(type: .system)
button.digit = $0
button.setTitle("\($0)", for: .normal)
button.titleLabel?.font = .preferredFont(forTextStyle: .largeTitle)
button.setTitleColor(.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.darkGray.cgColor
button.accessibilityTraits = [.keyboardKey]
button.addTarget(self, action: #selector(didTapDigitButton(_:)), for: .touchUpInside)
return button
}

var deleteButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("⌫", for: .normal)
button.titleLabel?.font = .preferredFont(forTextStyle: .largeTitle)
button.setTitleColor(.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.darkGray.cgColor
button.accessibilityTraits = [.keyboardKey]
button.accessibilityLabel = "Delete"
button.addTarget(self, action: #selector(didTapDeleteButton(_:)), for: .touchUpInside)
return button
}()

lazy var decimalButton: UIButton = {
let button = UIButton(type: .system)
let decimalSeparator = Locale.current.decimalSeparator ?? "."
button.setTitle(decimalSeparator, for: .normal)
button.titleLabel?.font = .preferredFont(forTextStyle: .largeTitle)
button.setTitleColor(.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.darkGray.cgColor
button.accessibilityTraits = [.keyboardKey]
button.accessibilityLabel = decimalSeparator
button.addTarget(self, action: #selector(didTapDecimalButton(_:)), for: .touchUpInside)
return button
}()

init(target: UIKeyInput & UITextInput, useDecimalSeparator: Bool = false) {
self.target = target
self.useDecimalSeparator = useDecimalSeparator
super.init(frame: .zero)
configure()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

// MARK: - Actions

extension NumericKeyboard {
@objc func didTapDigitButton(_ sender: DigitButton) {
insertText("\(sender.digit)")
}

@objc func didTapDecimalButton(_ sender: DigitButton) {
insertText(Locale.current.decimalSeparator ?? ".")
}

@objc func didTapDeleteButton(_ sender: DigitButton) {
target?.deleteBackward()
}
}

// MARK: - Private initial configuration methods

private extension NumericKeyboard {
func configure() {
autoresizingMask = [.flexibleWidth, .flexibleHeight]
addButtons()
}

func addButtons() {
let stackView = createStackView(axis: .vertical)
stackView.frame = bounds
stackView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(stackView)

for row in 0 ..< 3 {
let subStackView = createStackView(axis: .horizontal)
stackView.addArrangedSubview(subStackView)

for column in 0 ..< 3 {
subStackView.addArrangedSubview(numericButtons[row * 3 + column + 1])
}
}

let subStackView = createStackView(axis: .horizontal)
stackView.addArrangedSubview(subStackView)

if useDecimalSeparator {
subStackView.addArrangedSubview(decimalButton)
} else {
let blank = UIView()
blank.layer.borderWidth = 0.5
blank.layer.borderColor = UIColor.darkGray.cgColor
subStackView.addArrangedSubview(blank)
}

subStackView.addArrangedSubview(numericButtons[0])
subStackView.addArrangedSubview(deleteButton)
}

func createStackView(axis: NSLayoutConstraint.Axis) -> UIStackView {
let stackView = UIStackView()
stackView.axis = axis
stackView.alignment = .fill
stackView.distribution = .fillEqually
return stackView
}

func insertText(_ string: String) {
guard let range = target?.selectedRange else { return }

if let textField = target as? UITextField, textField.delegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) == false {
return
}

if let textView = target as? UITextView, textView.delegate?.textView?(textView, shouldChangeTextIn: range, replacementText: string) == false {
return
}

target?.insertText(string)
}
}

// MARK: - UITextInput extension

extension UITextInput {
var selectedRange: NSRange? {
guard let textRange = selectedTextRange else { return nil }

let location = offset(from: beginningOfDocument, to: textRange.start)
let length = offset(from: textRange.start, to: textRange.end)
return NSRange(location: location, length: length)
}
}

Then you can:

textField.inputView = NumericKeyboard(target: textField)

That yields:

Sample Image

Or, if you want a decimal separator, too, you can:

textField.inputView = NumericKeyboard(target: textField, useDecimalSeparator: true)

The above is fairly primitive, but it illustrates the idea: Make you own input view and use the UIKeyInput protocol to communicate keyboard input to the control.

Also please note the use of accessibilityTraits to get the correct “Spoken Content” » “Speak Screen” behavior. And if you use images for your buttons, make sure to set accessibilityLabel, too.



Related Topics



Leave a reply



Submit