버튼을 장착한 Toolbar를 textField.inputAccessoryView에 넣어준다.

textView, textField에서 사용하기 때문에, Button을 들고 있는 커스텀 Toolbar객체를 정의했다.

import Combine
import UIKit

class BaseKeyboardToolbar: UIToolbar {
  // MARK: - Properties
  var tapPublisher: AnyPublisher<Void, Never> { self.tapSubject.eraseToAnyPublisher() }
  
  private let tapSubject: PassthroughSubject<Void, Never> = .init()
  
  private let doneButton: UIButton = {
    let btn = UIButton()
    btn.setTitle("완료", for: .normal)
    btn.setTitleColor(UIColor(fnColor: .gray3), for: .normal)
    return btn
  }()

  private var subscriptions = Set<AnyCancellable>()
  
  // MARK: - LifeCycle
  init() {
    super.init(frame: CGRect(
      x: .zero,
      y: .zero,
      width: UIScreen.main.bounds.width,
      height: 50
    ))
    self.setupToolbar()
    self.bind()
  }
  
  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  
  // MARK: - Private Helpers
  private func bind() {
    self.doneButton.publisher(for: .touchUpInside)
      .receive(on: RunLoop.main)
      .sink { [weak self] _ in
        self?.tapSubject.send()
      }
      .store(in: &self.subscriptions)
  }
  
  private func setupToolbar() {
    let emptySpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
    let barButtonItem = UIBarButtonItem(customView: self.doneButton)
    self.items = [emptySpace, barButtonItem]
  }
}

textField와 textView는 패딩을 위해 커스텀 객체로 사용하고 있어서, toolbar를 프로퍼티로 갖고 inputAccessoryView에 주입해주었다.

// in 커스텀 TextView
  private func setupToolbar() {
    self.inputAccessoryView = self.keyboardToolbar
  }

  private func bind() {
    self.keyboardToolbar.tapPublisher
      .receive(on: RunLoop.main)
      .sink { [weak self] _ in
        self?.endEditing(true)
      }
      .store(in: &self.subscriptions)
  }
// in 커스텀 TextField
  private func bind() {
    self.keyboardToolbar.tapPublisher
      .receive(on: RunLoop.main)
      .sink { [weak self] in
        self?.endEditing(true)
      }
      .store(in: &self.subscriptions)
  }
  
  private func setupToolbar() {
    self.inputAccessoryView = self.keyboardToolbar
  }

커스텀 객체에서 버튼 탭 시 endEditing이 호출된다면 viewController에서 번거롭게 keyboard down을 하지 않아도 되는 장점이 있다.