Закрываем клавиатуру iOS касанием: антипаттерны и 3 рабочих решения на Swift

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему клавиатура не закрывается сама?

Привет, коллега-разработчик! Если ты когда-нибудь писал форму входа, поисковую строку или мессенджер на iOS, то наверняка сталкивался с ситуацией: пользователь ввел текст, а клавиатура упрямо висит на экране. Кнопка "Готово" не нажата, пользователь тычет пальцем в пустое место, но ничего не происходит. Классика.

Это одна из самых популярных тем на Stack Overflow, где вопрос "Close iOS Keyboard by touching anywhere using Swift" набрал тысячи просмотров. Проблема не в том, что это сложно сделать. Проблема в том, что новички (и не только) часто выбирают неправильные или "грязные" способы. Давай разберем топ-3 ошибки, а затем посмотрим на три корректных, рабочих решения.

Сегодня мы говорим о Swift и UIKit. Если ты используешь SwiftUI — тоже загляни, принципы похожи.

Ошибка №1: "Вешаем TapGesture на всё подряд"

Самая частая ошибка — добавить UITapGestureRecognizer на UIView, но забыть отключить cancelsTouchesInView. В результате кнопки и таблицы перестают реагировать на нажатия. Пользователь тыкает в кнопку "Отправить", но срабатывает закрытие клавиатуры, а не отправка формы.

Пример неправильного кода:

override func viewDidLoad() {
    super.viewDidLoad()
    let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
    view.addGestureRecognizer(tap)
}

@objc func dismissKeyboard() {
    view.endEditing(true)
}

На первый взгляд — всё ок. Но на практике, если у тебя есть UITableView или UIButton, их нажатия могут "проглатываться" этим жестом. Особенно это заметно, когда ты добавляешь такой жест на scrollView.

Как исправить:

Добавь строчку tap.cancelsTouchesInView = false. Это позволит другим элементам интерфейса получать события касания.

let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)

Но даже это — не идеальное решение, если у тебя сложный интерфейс.

Ошибка №2: "Тап по пустому месту ломает таблицу"

Вторая популярная проблема — неправильное взаимодействие с UIScrollView или UITableView. Если ты добавляешь UITapGestureRecognizer на view, которая содержит таблицу, то при тапе по ячейке может срабатывать одновременно и жест, и метод didSelectRow. Или, что ещё хуже, жест будет перехватывать касание, и ячейка не будет выделяться.

Решение: используйте UIGestureRecognizerDelegate

Реализуй метод gestureRecognizer(_:shouldReceive:) и проверяй, был ли тап по текстовому полю или по другому элементу. Если касание пришлось на UITextField или UITextView — не блокируй жест.

extension ViewController: UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        // Не перехватываем касания, если они пришлись на текстовое поле
        if touch.view is UITextField || touch.view is UITextView {
            return false
        }
        return true
    }
}

Теперь жест будет срабатывать только при тапе по пустым областям, а текстовые поля останутся интерактивными.

Ошибка №3: "Забываем про возврат с клавиатуры"

Многие разработчики реализуют закрытие по тапу, но забывают про кнопку "Return" (Enter) на клавиатуре. Пользователь может нажать "Готово" и ожидать, что клавиатура закроется. Если этого не сделать — получишь негативный UX.

Как исправить:

Подпишись на протокол UITextFieldDelegate и реализуй метод textFieldShouldReturn:

class ViewController: UIViewController, UITextFieldDelegate {
    
    @IBOutlet weak var textField: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        textField.delegate = self
        setupTapGesture()
    }
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
    
    @objc func dismissKeyboard() {
        view.endEditing(true)
    }
}

Теперь клавиатура закроется и по тапу, и по нажатию "Return". Комбо-решение.

Правильный путь: три рабочих метода на Swift

Давай теперь соберем три корректных подхода, которые не ломают интерфейс и работают в 99% случаев.

Метод 1: Классический TapGesture (с делегатом)

Этот метод подходит для простых экранов. Мы используем UITapGestureRecognizer с делегатом, чтобы не блокировать другие элементы.

class ViewController: UIViewController, UIGestureRecognizerDelegate {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
        tap.delegate = self
        tap.cancelsTouchesInView = false
        view.addGestureRecognizer(tap)
    }
    
    @objc func dismissKeyboard() {
        view.endEditing(true)
    }
    
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        // Игнорируем касания по текстовым полям и кнопкам
        return !(touch.view is UIControl)
    }
}

Обрати внимание: UIControl — это базовый класс для кнопок, текстовых полей и других интерактивных элементов. Такой подход защищает от случайного перехвата.

Метод 2: Использование UIView.endEditing(_:) через IBOutlet

Если у тебя много текстовых полей, можно повесить метод на UITapGestureRecognizer прямо в Storyboard или XIB. В коде останется только один метод:

@IBAction func dismissKeyboardOnTap(_ sender: UITapGestureRecognizer) {
    view.endEditing(true)
}

В сториборде: перетащи Tap Gesture Recognizer на View, зажми Control и соедини с @IBAction. Не забудь в инспекторе атрибутов отключить cancels touches in view.

Метод 3: SwiftUI — модификатор .onTapGesture

Для тех, кто пишет на SwiftUI, решение ещё элегантнее. Используем модификатор .onTapGesture и вызов UIApplication.shared.endEditing().

import SwiftUI

struct ContentView: View {
    @State private var text: String =                

Блоги

Рекомендаций по книгам