チャットアプリでよくみるキーボードの動きに追随するTextFieldとButtoonを実装してみました。
開発環境
Xcode 12.3
Swift 5
デモ
こんなやつです。
コード
InputAccesoryViewController.swift
import UIKit
class InputAccesoryViewController: UIViewController {
let outputTableView = UITableView()
//プロパティにアクセスできるようにグローバル定数として定義
let inputTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Aa"
textField.backgroundColor = .systemGray6
textField.borderStyle = .roundedRect
return textField
}()
override var canBecomeFirstResponder: Bool { return true }
var inputContainerView: UIView!
//inputAccesoryViewの中身を作って返り値に入れます。
override var inputAccessoryView: UIView? {
if inputContainerView == nil {
//容器となるContainerViewを作成
inputContainerView = InputContainerView()
inputContainerView.backgroundColor = .systemYellow
//ContainerViewに定義しておいたTextFieldを追加
inputContainerView.addSubview(inputTextField)
//ContainerViewの高さがキーボードを閉じた時と開いた時で自動でリサイズする
inputContainerView.autoresizingMask = .flexibleHeight
//送信ボタンの追加
let sendButton = UIButton(type: .system)
sendButton.setImage(UIImage(systemName: "paperplane"), for: .normal)
//Button Actionを追加
sendButton.addTarget(self, action: #selector(didTapSend), for: .touchUpInside)
//ContainerViewにSendButtonを追加
inputContainerView.addSubview(sendButton)
//それぞれの部品に制約を設定します
sendButton.anchor(
right: inputContainerView.trailingAnchor,
paddingRight: 8,
width: 50,
height: 50)
inputTextField.anchor(
top: inputContainerView.topAnchor,
left: inputContainerView.leadingAnchor,
bottom: inputContainerView.layoutMarginsGuide.bottomAnchor,
right: sendButton.leadingAnchor,
paddingTop: 8,
paddingLeft: 8,
paddingBottom: 8,
paddingRight: 8)
}
//作成したContainerViewを返り値に入れる
return inputContainerView
}
var messages = [String()]
override func viewDidLoad() {
super.viewDidLoad()
inputTextField.delegate = self
outputTableView.dataSource = self
outputTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
//これでTableViewのスクロールに合わせてキーボードが閉じれるようになる
outputTableView.keyboardDismissMode = .interactive
view = outputTableView
}
@objc func didTapSend() {
if let message = inputTextField.text, inputTextField.text != "" {
messages.append(message)
inputTextField.text = ""
outputTableView.reloadData()
}
}
}
extension InputAccesoryViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
didTapSend()
textField.resignFirstResponder()
return true
}
}
extension InputAccesoryViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "cell")
cell.textLabel?.text = messages[indexPath.row]
return cell
}
}
InputContainerView.swift
InputContainerView.swift
import UIKit
class InputContainerView: UIView {
//これは、inputAccesoryViewが自動レイアウト制約から適切なサイズになるために必要です
override var intrinsicContentSize: CGSize {
return CGSize.zero
}
}
UIView Extension
制約を容易にする為にメソッドを作成しました。
UIView+.swift
import UIKit
extension UIView {
func anchor(top: NSLayoutYAxisAnchor? = nil,
left: NSLayoutXAxisAnchor? = nil,
bottom: NSLayoutYAxisAnchor? = nil,
right: NSLayoutXAxisAnchor? = nil,
paddingTop: CGFloat = 0,
paddingLeft: CGFloat = 0,
paddingBottom: CGFloat = 0,
paddingRight: CGFloat = 0,
width: CGFloat? = nil,
height: CGFloat? = nil) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
}
if let left = left {
leadingAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
}
if let right = right {
trailingAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
}
if let width = width {
widthAnchor.constraint(equalToConstant: width).isActive = true
}
if let height = height {
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
}
苦戦したところ
SafeAreaの部分に作成したContainerViewが被って表示され、意図した結果がなかなか出なかった。
textFeild.bottomAnchor
を
inputContainerView.layoutMarginsGuide.bottomAnchor
に指定するのがミソでした。
参考
こちらの記事を参考にし、解決することが出来ました。
iPhone X how to handle View Controller inputAccessoryView
何か間違いやより良い方法がありましたら優しく教えていただけると幸いです🙇♂️