UIButtonからUIPickerViewを表示させる方法

# はじめに
- 個人で家計簿アプリを作っています！その際、ButtonからPickerを表示、選択から保存までで躓いた部分が多く、記事をあまり見かけなかったので投稿させていただきます
- 変更した方が良い部分や、もっとうまい書き方等ありましたら、ご指摘いただけると幸いです

# 全体像
- 左画面の右下にある丸ボタンで右の画面に遷移
- 右画面の赤い四角で囲っているトレーボタンを押下するとPickerを表示、選択によって値を画像を変更する
- 右画面はTableViewを使用、xibでカスタムセルを用意

<img width="400" alt="Screen Shot 2022-12-21 at 22.13.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982100/f87ac2f9-de0c-44b9-89f5-725cdea3ac1a.png">
<img width="400" alt="Screen Shot 2022-12-21 at 22.42.52のコピー.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982100/856c9a28-06ee-da08-9537-c41b62ad8e96.png">


# 躓いた点
- カスタムセルがUIViewを継承していないため、検索でよく出てくる基本的な方法で実装できなかったこと
- 選択されたpickerの項目を画像に変換し、表示させること

# 実現できたこと
- tableviewのカスタムセル内にあるUIButtonをタップした際に、textfield風のPickerViewを表示させること
- PickerViewで選択した項目によって、ボタンの画像を切り替えること
   
# コード
-  UIButtonを継承したクラスを用意し、PickerViewKeyboardDelegateとUIPickerViewを持たせる
-  実際にボタンがタップされたときに呼ばれる処理を記載

### PickerViewKeyboardクラス 
```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
        pickerView.dataSource = self
        let row = delegate.initSelectedRow(sender: self)
        pickerView.selectRow(row, inComponent: 0, animated: true)
        
        return pickerView
    }
```

- Picker実装の際の必須のコード
```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]
    }
}

```

- PickerViewKeyboard の delegateのプロトコル
- pickerが出た時の完了ボタンや、キャンセルボタンの処理など
```swift
protocol PickerViewKeyboardDelegate {
    func titlesOfPickerViewKeyboard(sender: PickerViewKeyboard) -> Array<String>
    func initSelectedRow(sender: PickerViewKeyboard) -> Int
    func didCancel(sender: PickerViewKeyboard)
    func didDone(sender: PickerViewKeyboard, selectedData: String)
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) -> String
}
```

### NewCreateLogTableViewCell(カスタムセルのクラス)

```swift
class NewCreateLogTableViewCell: UITableViewCell {
// 省略
    // Imageとなってますが、buttonです
    @IBOutlet weak var categoryImage: PickerViewKeyboard!
    
    let dataSource: [String] = ["生活費","経費","娯楽費"]

    override func awakeFromNib() {
        categoryImage.delegate = self
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }
// 省略
}

extension NewCreateLogTableViewCell: PickerViewKeyboardDelegate {

    func titlesOfPickerViewKeyboard(sender: PickerViewKeyboard) -> Array<String> {
        return dataSource
    }
    
    func initSelectedRow(sender: PickerViewKeyboard) -> Int {
        return dataSource.count
    }

// pickerが選択された時、画像を切り替える
// 画像名はenumで定義
 func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) -> String {
        if dataSource[row] == "生活費" {
                    self.categoryImage.setImage(UIImage(systemName: Category.livingCost.rawValue), for: UIControl.State())
                } else if dataSource[row] == "経費" {
                    self.categoryImage.setImage(UIImage(systemName: Category.expenses.rawValue), for: UIControl.State())
                } else {
                    self.categoryImage.setImage(UIImage(systemName: Category.waste.rawValue), for: UIControl.State())
                }
        return dataSource[row]

    func didDone(sender: PickerViewKeyboard, selectedData: String) {
        //pickerを閉じる処理
        self.endEditing(true)
        print("didDone")
        }
    
    
    func didCancel(sender: PickerViewKeyboard) {
        //pickerを閉じる処理
        self.endEditing(true)
        print("canceled")
    }
}

```
# 完成動画
![picker完成.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982100/ee1a0a27-6ac6-70c6-8ad8-656cf6a9239f.gif)



# 終わりに
- delegateの使い方など、初歩的な部分も含めて学習することができとても良い経験になった
- まだまだ改善できる部分は数多くあると思うので、改善してより良く記述できるようになりたい
- 早くアプリを完成まで持っていきたい

# 参考文献
https://i-app-tec.com/ios/uipickerview.html

https://majintools.com/2018/10/13/pickerview/

https://qiita.com/wai21/items/765206fdc37b2c258481

## 最後に
弊社では、経験の有無を問わず採用を行っています。
興味のある方は是非カジュアル面談しましょう！

https://jambo-support.com/recruit_engineer/