I’m building a chat feature for my iOS app with a custom input bar (UITextView and "Send" button) that stays attached to the keyboard. When the user types, the input bar behaves as expected and moves with the keyboard. However, when the user swipes down to dismiss the keyboard, the input bar stays in its last position (midway up the screen) until the keyboard is fully dismissed, at which point the input bar jumps to the bottom of the screen.
What I Want: I want the input bar to follow the keyboard during the swipe-down gesture, staying attached to it in real time as the keyboard animates down.
Current Implementation: Here’s the relevant code for my ChatViewController:
import UIKit
class ChatViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextViewDelegate {
private let tableView = UITableView()
private let messageInputBar = UIView()
private let messageTextView = UITextView()
private let sendButton = UIButton(type: .system)
private var messageInputBarBottomConstraint: NSLayoutConstraint?
private var messageTextViewHeightConstraint: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
title = "Chat"
setupUI()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
let swipeDownGesture = UISwipeGestureRecognizer(target: self, action: #selector(dismissKeyboard))
swipeDownGesture.direction = .down
swipeDownGesture.cancelsTouchesInView = false
view.addGestureRecognizer(swipeDownGesture)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
private func setupUI() {
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
messageInputBar.translatesAutoresizingMaskIntoConstraints = false
messageInputBar.backgroundColor = .secondarySystemBackground
view.addSubview(messageInputBar)
messageTextView.font = UIFont.systemFont(ofSize: 16)
messageTextView.layer.cornerRadius = 18
messageTextView.layer.borderWidth = 1
messageTextView.layer.borderColor = UIColor.lightGray.cgColor
messageTextView.translatesAutoresizingMaskIntoConstraints = false
messageInputBar.addSubview(messageTextView)
let sendIcon = UIImage(systemName: "arrow.up")
sendButton.setImage(sendIcon, for: .normal)
sendButton.backgroundColor = .systemBlue
sendButton.layer.cornerRadius = 20
sendButton.translatesAutoresizingMaskIntoConstraints = false
messageInputBar.addSubview(sendButton)
messageInputBarBottomConstraint = messageInputBar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
NSLayoutConstraint.activate([
messageInputBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
messageInputBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
messageInputBarBottomConstraint!,
messageInputBar.heightAnchor.constraint(equalToConstant: 50)
])
NSLayoutConstraint.activate([
messageTextView.leadingAnchor.constraint(equalTo: messageInputBar.leadingAnchor, constant: 10),
messageTextView.topAnchor.constraint(equalTo: messageInputBar.topAnchor, constant: 7),
messageTextView.bottomAnchor.constraint(equalTo: messageInputBar.bottomAnchor, constant: -7),
messageTextView.trailingAnchor.constraint(equalTo: sendButton.leadingAnchor, constant: -10),
sendButton.widthAnchor.constraint(equalToConstant: 40),
sendButton.heightAnchor.constraint(equalToConstant: 40),
sendButton.trailingAnchor.constraint(equalTo: messageInputBar.trailingAnchor, constant: -10),
sendButton.centerYAnchor.constraint(equalTo: messageInputBar.centerYAnchor)
])
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: messageInputBar.topAnchor)
])
}
@objc func keyboardWillShow(notification: NSNotification) {
if let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardHeight = keyboardFrame.cgRectValue.height
UIView.animate(withDuration: 0.3) {
self.messageInputBarBottomConstraint?.constant = -keyboardHeight
self.view.layoutIfNeeded()
}
}
}
@objc func keyboardWillHide(notification: NSNotification) {
UIView.animate(withDuration: 0.3) {
self.messageInputBarBottomConstraint?.constant = 0
self.view.layoutIfNeeded()
}
}
@objc private func dismissKeyboard() {
view.endEditing(true)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0 // No messages yet
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return UITableViewCell()
}
}`