При разработке приложений для iOS мы часто сталкиваемся с проблемами, когда клавиатура iPhone выдвигается вверх и закрывает UITextField / UITextView
Работа с клавиатурами в iOS всегда была проблемой. Независимо от того, новичок вы или опытный разработчик, вам всегда нужно будет работать с клавиатурой в ваших приложениях.
Управление клавиатурой - это базовый функционал, но Apple вынуждает нас управлять им вручную.
До iOS 15 было несколько способов решить проблему когда клавиатура перекрывает UITextField / UITextView или другие элементы.
Можно было вынести в свойство или аутлет нижний констрейнт перекрываемого элемента. Подписаться на уведомления
UIKeyboardWillShowNotification
UIKeyboardWillHideNotification
И при наступлении соответствующего события увеличивать или уменьшать констрейнт на высоту клавиатуры.
Либо можно было разместить UI элементы на scrollview и при появлении и сворачивании клавиатуры задавать соответствующий офсет scrollview.
keyboardLayoutGuide
В iOS 15 появилась новая возможность изменять наш лейоут при появлении клавиатуры: keyboardLayoutGuide. Про эту новую возможность и хочется рассказать в данной статье на примере простого тестового приложения, использующего UITextView.
Для начала в файле ViewController создадим textView и кнопку для сворачивания клавиатуры.
private let textView: UITextView = {
let view = UITextView()
view.isEditable = true
view.backgroundColor = .secondarySystemFill
view.translatesAutoresizingMaskIntoConstraints = false
view.font = .systemFont(ofSize: 22, weight: .regular)
return view
}()
private let hideKeyboardButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .systemMint
button.setTitleColor(.white, for: .normal)
button.setTitleColor(.gray, for: .highlighted)
button.setTitle("PRESS ME", for: .normal)
button.layer.cornerRadius = 22
return button
}()
В методе viewDidLoad зададим нашему view цвет фона, добавим textView и кнопку на view и установим соответствующие констрейнты, вызвав метод setupConstaraints (его мы рассмотрим чуть позже).
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
view.addSubview(textView)
view.addSubview(hideKeyboardButton)
hideKeyboardButton.addTarget(self,
action: #selector(buttonDidTap),
for: .touchUpInside)
setupConstaraints()
}
Сделаем так, что при загрузке экрана textView сразу же становиться активным.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
textView.becomeFirstResponder()
}
Реализуем метод скрытия клавиатуры, который вызывается при нажатии кнопки.
@objc private func buttonDidTap() {
textView.resignFirstResponder()
}
Теперь перейдем к установке констрейнтов. Именно здесь мы и воспользуемся новыми возможностями iOS 15, использую keyboardLayoutGuide.
private func setupConstaraints() {
NSLayoutConstraint.activate([
textView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
textView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10),
textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10),
textView.bottomAnchor.constraint(equalTo: hideKeyboardButton.topAnchor, constant: -10),
hideKeyboardButton.widthAnchor.constraint(equalToConstant: 300),
hideKeyboardButton.heightAnchor.constraint(equalToConstant: 50),
hideKeyboardButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
// MARK: - KeyboardLayoutGuide
hideKeyboardButton.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor, constant: -10),
])
}
В строке 6 мы привязали нижнюю границу textView к верхней границе кнопки. А в строке 14 мы привязываем нижнюю границу кнопки к верхней границе клавиатуры через keyboardLayoutGuide.
Теперь при появлении и скрытии клавиатуры наш UI подстраивается автоматически.
Надеюсь эта статья окажется полезной и поможет упростить обработку событий клавиатуры в ваших приложениях.
storoj
Но только так убивается вся суть iOS7+ дизайна: весь прикол был в том, что это не вся скроллвью смещается наверх. Скроллвью оставалась такой же фулскриновой, и ей лишь добавлялся нижний contentInset. Таким образом при скролле контента он мог заезжать под клавиатуру и быть чуть виден сквозь неё.
ws233
Более того, это поведение зашито в UITableViewController и работает из коробки. Если поместить UITextField в ячейку таблички, то даже строчки кода писать не придется – все взлетит, как есть.
Могу ошибаться, но это же поведение зашито и в UIScrollView. UITextView содержит в себе UIScrollView, а значит, что и в нем это поведение должно быть из коробки. Но если его вдруг нет, то лучше вешать contentInset на keyboardLayoutGuide, а не позицию компонента на экране.
Профиты "зашитого" поведения:
Не придется тратить время на написание того, что уже работает.
Не придется писать тесты своих велосипедов.
При последующих изменениях в ядре системы можно быть уверенным, что Apple о нас подумает и добавить поддержку старого кода и поведения.
storoj
К сожалению, поведение зашито только в UITableViewController и UICollectionViewController.
ws233
Значит, им там и место :)
А больше нигде и не нужно. И все попытки натянуть сову на глобус – неправильны в самой постановке реализации.
Либо пользуемся имеющимися контроллерами, либо создаем свой универсальный... Но мне сложно придумать такой универсальный контроллер, в котором мне бы пришлось добавлять поддержку работы с клавиатурой. Имеющихся двух вполне достаточно для решения проблемы, описанной в статье. Да и любой другой проблемы с клавиатурой.
storoj
Я бы так не сказал. Например, если захочется сделать UITextViewController ("полноэкранный" редактор текста), то будет проблема. Можно и другие примеры представить, когда нужна рутовая UIScrollView, в которой есть текстовый инпут.
ws233
Исключение, лишь подтверждающее правило?
storoj
Наверное да, но просто немного расстраивает факт того, что эта универсальная задача, относящаяся к любым UIScrollView, и даже кое-как имплементированная в изолированном объекте (
UIAutoRespondingScrollViewControllerKeyboardSupport
), недоступна к использованию в этих "особых" местах.