0

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()
}

}`

0