【Swift】InputAccessoryViewを使ってメッセージ入力を実装する

# はじめに
初めまして！
関西を拠点に活動している学生団体watnowの、アドベントカレンダー最終日を担当させていただきますアントニーと申します！

今回はチャット機能実装などの際に必要不可欠となる、メッセージ入力画面/機能を実装する簡単な方法を紹介させていただきます。
# InputAccessoryViewとは
UITextFieldやUITextViewを使用する際に出てくるキーボードにViewをアタッチするために使用します。
簡単に言うと、チャットで文章を送信するときの機能を簡単に作成できるんだなって思ってくれたら良いと思います。

# 今回実装したいこと
こちらが今回の完成形です。
文字を入力し、送信ボタンを押すとLabelに入力した文字が表示されます。
![InputAccessoryview.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358154/77a4dcf1-7c8a-c7ab-b8db-c2d27cbf961f.gif)

# 実装

まずはじめに、Xibファイルを作成します。
ViewのsizeはFreeformにしておきましょう。
![スクリーンショット 2021-12-25 22.22.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358154/98c0eca1-5ce7-4e7b-0a9b-6802d8fc9347.png)

上記で作成したXibファイルに紐付けるswiftファイルを作成します。
![スクリーンショット 2021-12-25 22.19.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358154/497a10a3-f9b5-1d71-6e87-652b4c236087.png)


### 画面作成
以下のようにUITextViewとUIButtonを配置します。
※オートレイアウトの設定を忘れないでください。
![スクリーンショット 2021-12-25 22.25.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358154/3848acaa-f105-44fc-d0c2-261aa328c727.png)




## コード説明
ここから、コードの説明をしていきます。
全体のコードは最後に記載してますので、お急ぎの方はそちらをご参照ください。  

まず、作成したswiftファイル(ここでは、"IndicateAccessoryView.swift"と名付けました。)に、以下のようにクラスを定義します。



```IndicateAccessoryView.swift
import UIKit

class IndicateAccessoryView: UIView {
    
    @IBOutlet var inputTextView: UITextView!
    @IBOutlet var sendButton: UIButton!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}

```

### TextViewの高さを可変にする



```IndicateAccessoryView.swift
 override init(frame: CGRect) {
        super.init(frame: frame)
        
        autoresizingMask = .flexibleHeight
    }

    override var intrinsicContentSize: CGSize {
        return .zero
    }
```


### Delegateを作成する

```IndicateAccessoryView.swift
protocol IndicateAccessoryViewDelegate: class {
    func tappedSendButton(text: String)
}
```

### XibファイルをIndidateInputAccessoryViewに表示する
以下のコードでXibファイルをViewに表示する処理を行なっています。

```IndicateAccessoryView.swift
private func nibInit() {
        let nib = UINib(nibName: "IndicateAccessoryView", bundle: nil)
        guard let view = nib.instantiate(withOwner: self, options: nil).first as? UIView else { return }
        
        view.frame = self.bounds
        view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
        self.addSubview(view)
    }

```


### IndicateAccessoryViewの全文


```IndicateAccessoryView.swift
import UIKit


protocol IndicateAccessoryViewDelegate: class {
    func tappedSendButton(text: String)
}

class IndicateAccessoryView: UIView {
    
    @IBOutlet var inputTextView: UITextView!
    @IBOutlet var sendButton: UIButton!
    
    var delegate: IndicateAccessoryViewDelegate?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        nibInit()
        autoresizingMask = .flexibleHeight
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
    @IBAction func tappedSendButton() {
        guard let text = inputTextView.text else { return }
        delegate?.tappedSendButton(text: text)  //textViewの情報を渡す準備
    }
    //chatTextViewの高さを可変にする(initでautoresizingMaskの設定必要)
    override var intrinsicContentSize: CGSize {
        return .zero
    }
    
    //XibファイルをChatInputAccessoryViewに表示するためのメソッド
    private func nibInit() {
        let nib = UINib(nibName: "IndicateAccessoryView", bundle: nil)
        guard let view = nib.instantiate(withOwner: self, options: nil).first as? UIView else { return }
        
        view.frame = self.bounds
        view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
        self.addSubview(view)
    }
    
    
}

```


## 紐付け
Xibファイルとswiftファイルの紐付けですが、Viewではなく、PlaceholdersのFile's Ownerに紐づけるように注意してください。
![スクリーンショット 2021-12-25 22.41.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358154/9b8dd452-43d0-0c64-6e3b-06b54a7589d6.png)


## MainStoryboardの設定
以下のように、送信したボタンを表示するラベルを配置しました。
![スクリーンショット 2021-12-25 22.45.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358154/caf0e95b-d142-d7d8-aee7-8a3d95a08c7d.png)


## ViewController.swiftのコード説明

### InputAccessoryViewの配置
以下のコードで配置します。

IndicateAccessoryView.swiftで作成しているので、その設定も行います。

```ViewController.swift
private lazy var indicateAccessoryView: IndicateAccessoryView = {
        let view = IndicateAccessoryView()
        view.frame = .init(x: 0, y: 0, width: view.frame.width, height: 150)
        view.delegate = self
        return view
    }()

override var inputAccessoryView: UIView? {
        get {
            return indicateAccessoryView
        }
    }

override var canBecomeFirstResponder: Bool {
        return true
    }
```

　送信ボタンがタップされた際、入力された文字を作成したラベルに表示するようにします。

```ViewController.swift
extension ViewController: IndicateAccessoryViewDelegate {
    func tappedSendButton(text: String) {
        sendLabel.text = text
    }
}
```
### コード全文

```ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet var sendLabel: UILabel!
    
    private lazy var indicateAccessoryView: IndicateAccessoryView = {
        let view = IndicateAccessoryView()
        view.frame = .init(x: 0, y: 0, width: view.frame.width, height: 150)
        view.delegate = self
        return view
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override var inputAccessoryView: UIView? {
        get {
            return indicateAccessoryView
        }
    }

    override var canBecomeFirstResponder: Bool {
        return true
    }
}

extension ViewController: IndicateAccessoryViewDelegate {
    func tappedSendButton(text: String) {
        sendLabel.text = text
    }
}

```

#終わりに
いかがでしたでしょうか。
文字入力する際に、キーボードも出てきてくれるし、消える時も一緒に消えてくれるし、すごく便利ではないでしょうか。
今回の実装では簡単化のために、cornerRadiusや送信後にtextViewのクリアなどは行いませんでしたが、これでかなり本格的なチャット機能を実装できるのではないでしょうか。