LoginSignup
10
8

More than 3 years have passed since last update.

inputViewを使ってカスタムキーボードをつくろう!(Swift)

Last updated at Posted at 2021-01-25

Xcode-12 Swift-5.3 iOS-14

はじめに

UITextFieldUITextView で入力する際はキーボードの入力タイプが色々用意されていますが実はこのキーボード色々カスタムができます!

たぶん実用的なのは UIPickerView とか UIDatePicker を表示したりとかです(なんか昔半角カナ用キーボードを作った記憶があるけどあんまよくなかった:innocent:)。

これからはもう UIKit を使うことも少なくなっていくのかもしれませんが備忘録として。。。

*注:「つくろう」って書きましたがとくにカスタムキーボードを薦めるものではありません。

UITextFieldをカスタマイズ

試しに UITextField のキーボードをカスタムしてみます。やり方は簡単で inputView に任意の View を設定するだけで OK です:v:

// こんなのとか
let keyboard = CustomKeyboardView(frame: .init(origin: .zero, size: .init(width: 284, height: 284)))
keyboard.delegate = self
textField.inputView = keyboard

// こんなのとか
let picker = UIPickerView()
picker.delegate = self
picker.dataSource = self
textField.inputView = picker

あとはデリゲートとかで入力文字を受け取って UITextField に表示するだけです。

こんな感じ。

カスタム View ピッカー
custom picker

カスタム View はこんな感じです。

xib

xib

protocol CustomKeyboardViewDelegate: AnyObject {
    func customKeyboardView(_ customKeyboardView: CustomKeyboardView, didSelectKey key: String)
}

final class CustomKeyboardView: UIView {

    weak var delegate: CustomKeyboardViewDelegate?
    override init(frame: CGRect) {
        super.init(frame: frame)
        loadNib()
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
        loadNib()
    }

    private func loadNib() {
        let view = Bundle.main.loadNibNamed("CustomKeyboardView", owner: self, options: nil)?.first as! UIView
        view.frame = bounds
        view.translatesAutoresizingMaskIntoConstraints = true
        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        addSubview(view)
    }

    @IBAction private func selectKey(_ sender: UIButton) {
        delegate?.customKeyboardView(self, didSelectKey: sender.titleLabel!.text!)
    }
}

キーボードの高さはわりと自由みたいです:raised_hands:

custom_2

サクッとカスタムキーボードが表示できました!ただカスタムキーボードを入力制限目的で利用するにはいくつか問題があるのでオススメしません:no_good:コピペとか外部キーボード接続とか。。。

コピペに関しては UITextField のカスタムクラスをつくって canPerformAction ごにょごにょすればいける気がしますが外部キーボードに関しては防げないと思われます(そもそもあんまり UITextField で入力制限なんてしない方がいいと思います)。

UIControlをカスタマイズ

とくに UITextFieldinputView に設定する!でも問題ないのですが UITextField を使うとなるとレイアウトの自由度が低いのでせっかくなんでカスタムクラスをつくった方が自由度が高いのでおすすめです:thumbsup:

下記のように UIControl を継承したクラスをつくって canBecomeFirstResponderinputView を設定してやるとカスタムキーボードが表示できます。UIView じゃないのは addAction がしたかったからです。フォーカス時がわかりにくいので枠線の色を変更しています。

protocol PickerInputControlDelegate: AnyObject {
    func pickerInputControl(_ pickerInputControl: PickerInputControl, didSelectValue value: String)
}

final class PickerInputControl: UIControl {

    var items = [String]()
    weak var delegate: PickerInputControlDelegate?

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
        commonInit()
    }

    private func commonInit() {
        layer.cornerRadius = 8.0
        layer.borderWidth = 1.0
        layer.borderColor = UIColor.systemGray.cgColor
        addAction(.init(handler: { [weak self] _ in
            self?.isSelected.toggle()
            if self?.isSelected == true {
                self?.becomeFirstResponder()
            } else {
                self?.resignFirstResponder()
            }
        }), for: .touchUpInside)
    }

    override var inputView: UIView? {
        let picker = UIPickerView()
        picker.delegate = self
        picker.dataSource = self
        return picker
    }

    override var canBecomeFirstResponder: Bool {
        return true
    }

    override var isSelected: Bool {
        didSet {
            layer.borderColor = isSelected ? UIColor.systemBlue.cgColor : UIColor.systemGray.cgColor
        }
    }

    @discardableResult
    override func becomeFirstResponder() -> Bool {
        let value = super.becomeFirstResponder()
        isSelected = isFirstResponder
        return value
    }

    @discardableResult
    override func resignFirstResponder() -> Bool {
        let value = super.resignFirstResponder()
        isSelected = isFirstResponder
        return value
    }
}

extension PickerInputControl: UIPickerViewDelegate {

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        delegate?.pickerInputControl(self, didSelectValue: items[row])
    }
}

extension PickerInputControl: UIPickerViewDataSource {

    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return items.count
    }

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return items[row]
    }
}

こんな感じで UIControl なので中に View 置き放題です。

control

UIPickerViewDelegate, UIPickerViewDataSource, items は外に出した方が汎用性が高いかも:innocent:

static cellと組み合わせる

おまけで UITableViewController の static cell と組み合わせるとフォーカス時に自動スクロールしてくれるので複数の入力項目がある場合はおすすめです:thumbsup:

こんな感じ。

static_cell

おわりに

カスタムキーボードを作る際はタップ領域をちゃんと確保してあげると素敵です。ちょっと古い記事ですが下記の記事はキーボードのタップ領域とか詳しく調べていておもしろいです。

iPhone の当たり判定を検証した

わりとお世話になった手法だけどこれからはあんまり使わなくなるのかな UIKit。。。

調べてて気になったけどこいつ知らねえ:sweat:

inputviewcontroller

参考

10
8
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
8