【Swift】キーボードにUIPickerViewを表示する

# １．はじめに

　`UITextView` や `UITextField` の入力時のキーボードに `UIPickerView` を表示する記事はよく見かけますが、`UIButton` 契機で表示する方法はあまりなかったようなので記事にしました。
　とはいえ、`UITextView` や `UITextField` とやることは特に変わりません。`UIButton` の　`inputView` をオーバーライドして`UIPickerView` を返すだけです。
　また、`UIResponder` を継承したクラス（`UIView` 系）は全て、`inputView` というプロパティを持ちます。ここに任意の`view` を設定することで、システムキーボードの代わりに表示させることができます。

# ２．環境
Xcode Version 9.2
Swift Version 4.0.3

# ３．コード① `PickerViewKeyboard` の本体

　`UIButton` を継承した`PickerViewKeyboard` を定義します。
　ポイントは以下の通りです。

- `canBecomeFirstResponder` をオーバーライドして`true` を返すことでキーボードを表示できるようにする
- `data` に`UIPickerView` のデータソースを定義する
- `init()` で`addTarget` する。当ボタンがタップされた際に`didTouchUpInside` が呼ばれるようにする。`didTouchUpInside` でキーボードを表示している
- `inputView` をオーバーライドし、`UIPickerView` を返すことで、キーボードに`UIPickerView` を表示する
- `inputAccessoryView` をオーバーライドし、各種ボタンを定義した`UIToolBar` を返す。ここで返した`view` がキーボード上部の`cancel` ボタンや `done` ボタンになる
- `cancelPicker()`、`donePicker()` で呼ぶdelegateメソッドは後ほど


```swift

class PickerViewKeyboard: UIButton {
    var delegate: PickerViewKeyboardDelegate!
    var pickerView: UIPickerView!
    
    override var canBecomeFirstResponder: Bool {
        return true
    }
    
    // ピッカーに表示させるデータ
    var data: Array<String> {
        return delegate.titlesOfPickerViewKeyboard(sender: self)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.addTarget(self, action: #selector(didTouchUpInside(_:)), for: .touchUpInside)
    }
    
    @objc func didTouchUpInside(_ sender: UIButton) {
        becomeFirstResponder()
    }
    
    override var inputView: UIView? {
        pickerView = UIPickerView()
        pickerView.delegate = self
        let row = delegate.initSelectedRow(sender: self)
        pickerView.selectRow(row, inComponent: 0, animated: true)

        return pickerView
    }
    
    override var inputAccessoryView: UIView? {
        let toolbar = UIToolbar()
        toolbar.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: 44)
        
        let space = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: self, action: nil)
        space.width = 12
        let cancelItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(PickerViewKeyboard.cancelPicker))
        let flexSpaceItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
        let doneButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(PickerViewKeyboard.donePicker))
        
        let toolbarItems = [space, cancelItem, flexSpaceItem, doneButtonItem, space]
        
        toolbar.setItems(toolbarItems, animated: true)
        
        return toolbar
    }
    
    @objc func cancelPicker() {
        delegate.didCancel(sender: self)
    }
    
    @objc func donePicker() {
        delegate.didDone(sender: self, selectedData: data[pickerView.selectedRow(inComponent: 0)])
    }
}
```

# ４．コード② `UIPickerView` のdelegateメソッド
　ピッカービューの各種定義を行います。
　ポイントは以下の通りです。

- `UIPickerViewDelegate`、`UIPickerViewDataSource` の適合を忘れずに
- `pickerView(_, didSelectRow, inComponent)` でピッカービューの値が変わった時の処理を実装する。ここでは選択したインデックスを退避

```swift

extension PickerViewKeyboard: UIPickerViewDelegate, UIPickerViewDataSource {
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return data.count
    }
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return data[row]
    }
}

```

# ５．コード③ `PickerViewKeyboardDelegate` の定義
　データソースの取得やキーボードを閉じる際に呼ばれるデリゲートメソッドを定義します。
　ポイントは以下の通りです。
- `titlesOfPickerViewKeyboard` でデータソースを返す
- `didCancel`、`didDone` でキーボードのアクセサリービューのボタンをタップした際の処理を定義する

```swift

protocol PickerViewKeyboardDelegate {
    func titlesOfPickerViewKeyboard(sender: PickerViewKeyboard) -> Array<String>
    func initSelectedRow(sender: PickerViewKeyboard) -> Int
    func didCancel(sender: PickerViewKeyboard)
    func didDone(sender: PickerViewKeyboard, selectedData: String)
}
```

# ６．使い方
 `storyboard` で`UIButton` をビューに配置します。カスタムクラスに`PickerViewKeyboard` を設定します（スクリーンショット参照）。
　`ViewController` にアウトレット接続し、`viewDidLoad` などで`PickerViewKeyboard` の`delegate` に`self` を設定します。
　下記の`ViewController` ではキーボードのピッカービューから取得した値を`UILabel` に設定しています。

<img width="1184" alt="スクリーンショット.png" src="https://qiita-image-store.s3.amazonaws.com/0/124577/60dcc869-7cb2-ce49-0d7f-74c588db8387.png">

```swift

class ViewController: UIViewController {
    @IBOutlet weak var pickerKeyboardButton: PickerViewKeyboard!
    @IBOutlet weak var resultLabel: UILabel!
    let dataSource =  ["いち", "に", "さん", "よん", "ご", "ろく", "なな"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        pickerKeyboardButton.delegate = self
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }    
}

extension ViewController: PickerViewKeyboardDelegate {
    func titlesOfPickerViewKeyboard(sender: PickerViewKeyboard) -> Array<String> {
        return dataSource
    }
    func initSelectedRow(sender: PickerViewKeyboard) -> Int {
        return 3
    }
    func didDone(sender: PickerViewKeyboard, selectedData: String) {
        resultLabel.text = selectedData
    }
    func didCancel(sender: PickerViewKeyboard) {
        print("canceled")
    }
}

```

ボタンをタップすると画面下部のキーボードのエリアにピッカービューが表示されます。
Doneボタンをタップすると選択中の文字列がLabelに表示されます。

![スクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/124577/84ab7854-8602-4861-b0c7-00900fc48746.png)

![スクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/124577/01232ec6-1ab4-0035-7144-99ede3306290.png)


間違っている点や改善点などありましたら、ぜひご指摘をお願いします。