高さ可変のinputViewを表示する（Swift）

![Xcode-12](https://img.shields.io/badge/Xcode-12-brightgreen) ![Swift-5.3](https://img.shields.io/badge/Swift-5.3-brightgreen) ![iOS-14](https://img.shields.io/badge/iOS-14-brightgreen)

## はじめに
以前書いた下記記事でキーボードの高さもいい感じになってると思っていたのですがそうでもなかった模様:innocent:

https://qiita.com/am10/items/d554f860f9081dc636e7

`inputAccessoryView` を表示するとわかるのですがカスタムの `inputView` が表示領域を飛び出していた。

<img src="https://user-images.githubusercontent.com/34936885/115107435-cc3e7e80-9fa5-11eb-96e6-8e8053a3baa6.jpeg" width=300>

これをいい感じに表示したい。ついでに高さを可変にしてみます。

## いろいろチャレンジ
`CustomInputView` っていうのを xib で作成して使用しています。  
どうにか高さを設定してやればいいと思い色々試してみました。

### オートレイアウト
下記のように高さの制約をつけてみましたが高さ 500 にはなりませんでした:neutral_face:（通常のキーボードと同じ高さになりました）。

```swift
let v = CustomInputView()
v.translatesAutoresizingMaskIntoConstraints = false
v.heightAnchor.constraint(equalToConstant: 500).isActive = true
textField.inputView = v
```

<img src="https://user-images.githubusercontent.com/34936885/115107405-9ef1d080-9fa5-11eb-9958-7af2fdc5dd8b.png" width=300>

詳しくわからないですが `inputView` に制約を追加してもまだ add されてないから制約が効かないとかなんでしょうか:thinking:

### frame設定
`UIView` の `frame` に高さを設定してみるとそれっぽい表示になりました:raised_hands:

```swift
let v = CustomInputView()
var frame = v.frame
frame.size.height = 500
v.frame = frame
textField.inputView = v
```

<img src="https://user-images.githubusercontent.com/34936885/115107398-9a2d1c80-9fa5-11eb-9880-c51641f2aa55.png" width=300>

この方法でいけそう:thinking:

## 高さを可変にする
上記のように `frame` 設定で高さ調整ができました。ここからは需要があるかわかりませんが高さを可変にすることに挑戦します。

方法としては xib で Auto Layout を設定して、`frame.height` に `systemLayoutSizeFitting` で計算した高さを設定してやればいけるはずです。

完成形:sunglasses:（とくに意味はないですがチェックボックスを並べてみました）  

![custom_input](https://user-images.githubusercontent.com/34936885/115107528-571f7900-9fa6-11eb-903d-741141bd2165.gif)

ソース

```swift
protocol CustomKeyboardViewDelegate: AnyObject {
    func customKeyboardView(_ customKeyboardView: CustomKeyboardView, didToggleButton checked: CustomKeyboardView.Checked)
}

final class CustomKeyboardView: UIView {

    struct Hidden {
        let isFirst: Bool
        let isSecond: Bool
        let isThird: Bool
        let isFourth: Bool
        let isFifth: Bool
        let isSixth: Bool
    }

    struct Checked {
        let isFirst: Bool
        let isSecond: Bool
        let isThird: Bool
        let isFourth: Bool
        let isFifth: Bool
        let isSixth: Bool
    }

    @IBOutlet weak var firstButton: UIButton!
    @IBOutlet weak var secondButton: UIButton!
    @IBOutlet weak var thirdButton: UIButton!
    @IBOutlet weak var fourthButton: UIButton!
    @IBOutlet weak var fifthButton: UIButton!
    @IBOutlet weak var sixthButton: UIButton!

    weak var delegate: CustomKeyboardViewDelegate?

    func setupDefaultValue(hidden: Hidden, checked: Checked) {
        firstButton.isHidden = hidden.isFirst
        secondButton.isHidden = hidden.isSecond
        thirdButton.isHidden = hidden.isThird
        fourthButton.isHidden = hidden.isFourth
        fifthButton.isHidden = hidden.isFifth
        sixthButton.isHidden = hidden.isSixth

        firstButton.isSelected = checked.isFirst
        secondButton.isSelected = checked.isSecond
        thirdButton.isSelected = checked.isThird
        fourthButton.isSelected = checked.isFourth
        fifthButton.isSelected = checked.isFifth
        sixthButton.isSelected = checked.isSixth
    }

    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 toggleButton(_ sender: UIButton) {
        sender.isSelected.toggle()
        delegate?.customKeyboardView(self, didToggleButton: .init(
            isFirst: firstButton.isSelected, isSecond: secondButton.isSelected,
            isThird: thirdButton.isSelected, isFourth: fourthButton.isSelected,
            isFifth: fifthButton.isSelected, isSixth: sixthButton.isSelected
        ))
    }
}
```

```swift
class FirstViewController: UIViewController {

    @IBOutlet private weak var textField: UITextField!
    private var isFirstHidden: Bool = false
    private var isSecondHidden: Bool = false
    private var isThirdHidden: Bool = false
    private var isFourthHidden: Bool = false
    private var isFifthHidden: Bool = false
    private var isSixthHidden: Bool = false

    private var isFirstChecked: Bool = false
    private var isSecondChecked: Bool = false
    private var isThirdChecked: Bool = false
    private var isFourthChecked: Bool = false
    private var isFifthChecked: Bool = false
    private var isSixthChecked: Bool = false

    override func viewDidLoad() {
        super.viewDidLoad()
        let toolbar = UIToolbar()
        toolbar.items = [
            UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil),
            UIBarButtonItem.init(title: "閉じる", style: .done, target: self, action: #selector(close))
        ]
        toolbar.sizeToFit()
        textField.inputAccessoryView = toolbar
    }

    @objc private func close() {
        textField.resignFirstResponder()
    }

    @IBAction private func pattern1(_ sender: Any) {
        textField.resignFirstResponder()
        isFirstHidden = false
        isSecondHidden = false
        isThirdHidden = false
        isFourthHidden = false
        isFifthHidden = false
        isSixthHidden = false
        setupInputView()
        textField.becomeFirstResponder()
    }

    @IBAction private func pattern2(_ sender: Any) {
        textField.resignFirstResponder()
        isFirstHidden = false
        isSecondHidden = true
        isThirdHidden = false
        isFourthHidden = true
        isFifthHidden = false
        isSixthHidden = true
        setupInputView()
        textField.becomeFirstResponder()
    }

    @IBAction private func pattern3(_ sender: Any) {
        textField.resignFirstResponder()
        isFirstHidden = false
        isSecondHidden = false
        isThirdHidden = true
        isFourthHidden = true
        isFifthHidden = true
        isSixthHidden = true
        setupInputView()
        textField.becomeFirstResponder()
    }

    private func setupInputView() {
        let view = CustomKeyboardView()
        view.delegate = self
        let hidden: CustomKeyboardView.Hidden =
            .init(isFirst: isFirstHidden, isSecond: isSecondHidden, isThird: isThirdHidden,
                  isFourth: isFourthHidden, isFifth: isFifthHidden, isSixth: isSixthHidden)
        let checked: CustomKeyboardView.Checked =
            .init(isFirst: isFirstChecked, isSecond: isSecondChecked, isThird: isThirdChecked,
                  isFourth: isFourthChecked, isFifth: isFifthChecked, isSixth: isSixthChecked)
        view.setupDefaultValue(hidden: hidden, checked: checked)
        var frame = view.frame
        frame.size.height = view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
        view.frame = frame
        textField.inputView = view
    }
}

extension FirstViewController: CustomKeyboardViewDelegate {
    func customKeyboardView(_ customKeyboardView: CustomKeyboardView, didToggleButton checked: CustomKeyboardView.Checked) {
        isFirstChecked = checked.isFirst
        isSecondChecked = checked.isSecond
        isThirdChecked = checked.isThird
        isFourthChecked = checked.isFourth
        isFifthChecked = checked.isFifth
        isSixthChecked = checked.isSixth
    }
}
```

高さも可変でチェック状態も引継げる `inputView` ができました:clap:

## おわりに
使い道があるかは知りませんが無事高さ可変の `inputView` ができました:tada:

他にも下記のような方法があるみたいです。

https://stackoverflow.com/questions/24756018/custom-inputview-with-dynamic-height-in-ios-8/40359406