SwiftUIでUITextViewの上にtoolbarをのせる

画像のようにキーボードの上にツールバーがのったUIを作成します。
要件は以下のとおりです。
1. キーボードの上にツールバーが表示する
2. ツールバーのボタンをタップするとキーボードが閉じる
3. タップ時にログに入力した文字列を表示する

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/551042/3d90c9f8-3b07-7cfa-32cb-cb3243578d69.jpeg" width="300" />

`UITextView`を`UIViewRepresentable`に準拠させる実装を行います。

### `makeUIView`
この関数で`UITextView`を返します。
また、`UIToolbar`を上部にのせます。

```swift:UITextViewRepresentable.swift
struct UITextViewRepresentable: UIViewRepresentable {
    @Binding var text: String
    private let textView = UITextView()

    func makeUIView(context: Context) -> UITextView {
        textView.delegate = context.coordinator
        textView.autocapitalizationType = .none
        textView.autocorrectionType = .no
        textView.keyboardType = keyboardType
        
        let toolbar = UIToolbar(frame: CGRect(x: .zero, y: .zero, width: textView.frame.size.width, height: 44))
        let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
        let doneButton = UIBarButtonItem(
            title: "Done",
            style: .done,
            target: context.coordinator,
            action: #selector(context.coordinator.onTapDone(_:))
        )
        toolbar.setItems([spacer, doneButton], animated: true)
        textView.inputAccessoryView = toolbar
        
        return textView
    }
    ...
}
```

### `Coordinator`
Doneボタンをタップした時の処理を記述します。
ボタンの`addTarget`に指定できるように`@objc`修飾子を記載します。

```swift:UITextViewRepresentable.swift
struct UITextViewRepresentable: UIViewRepresentable {
    ...
    class Coordinator: NSObject, UITextViewDelegate {
        private let parent: UITextViewRepresentable
        
        init(_ parent: UITextViewRepresentable) {
            self.parent = parent
        }

        @objc func onTapDone(_ button: UIButton) {
            print(parent.textView.text)
        }
    }
    ...
}
```

### キーボードを閉じる処理
`UIApplication`のエクステンションを作りました。
この関数をボタンタップ時に実行します。

```swift:UIApplication+.swift
import UIKit

extension UIApplication {
    func endEditing() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}
```

全体のソースコードはこちらです。

```swift:UITextViewRepresentable.swift
struct UITextViewRepresentable: UIViewRepresentable {
    @Binding var text: String
    private let textView = UITextView()
    
    private let keyboardType: UIKeyboardType
    
    init(
        text: Binding<String>,
        keyboardType: UIKeyboardType
    ) {
        self._text = text
        self.keyboardType = keyboardType
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIView(context: Context) -> UITextView {
        textView.delegate = context.coordinator
        textView.autocapitalizationType = .none
        textView.autocorrectionType = .no
        textView.keyboardType = keyboardType
        
        let toolbar = UIToolbar(frame: CGRect(x: .zero, y: .zero, width: textView.frame.size.width, height: 44))
        let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
        let doneButton = UIBarButtonItem(
            title: "Done",
            style: .done,
            target: context.coordinator,
            action: #selector(context.coordinator.onTapDone(_:))
        )
        toolbar.setItems([spacer, doneButton], animated: true)
        textView.inputAccessoryView = toolbar
        
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
    }
    
    class Coordinator: NSObject, UITextViewDelegate {
        private let parent: UITextViewRepresentable
        
        init(_ parent: UITextViewRepresentable) {
            self.parent = parent
        }

        @objc func onTapDone(_ button: UIButton) {            
            print(parent.textView.text)
            UIApplication.shared.endEditing()
        }
    }
}
```

`UITextField`でも同じような方法で要件を実現できると思います。
このビューに渡されるtextは親ビューのテキストとバインドされてるので、親ビューは常に最新の文字列を取得できます。