Use notification observers to handle keyboard show and hide events:
// keyboard will show
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowAction(_:)), name: .UIKeyboardWillShow, object: nil)
// keyboard will hide
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHideAction(_:)), name: .UIKeyboardWillHide, object: nil)
You can do something like this when the keyboard is about to appear:
// keyboard will show action
@objc private func keyboardWillShowAction(_ notification: NSNotification) {
currentScrollViewOffset = scrollView.contentOffset
let keyboardFrame = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue
let keyboardHeight = keyboardFrame?.cgRectValue.height
let padding: CGFloat = 32
// if text field view would be obstructed by keyboard, apply minimum-needed scroll view offset
if activeTextFieldMaxYOnScreen! > (UIScreen.main.bounds.height - keyboardHeight! - padding) {
let temporaryOffset = activeTextFieldMaxYOnScreen! - (UIScreen.main.bounds.height - (keyboardHeight! + padding))
scrollView.setContentOffset(CGPoint(x: 0, y: currentScrollViewOffset.y + temporaryOffset), animated: true)
}
scrollView.addGestureRecognizer(tapToDismissKeyboard)
scrollView.isScrollEnabled = false
}
And then revert when the keyboard is dismissed:
// keyboard will hide action
@objc private func keyboardWillHideAction(_ notification: NSNotification) {
scrollView.setContentOffset(currentScrollViewOffset, animated: true)
scrollView.removeGestureRecognizer(tapToDismissKeyboard)
scrollView.isScrollEnabled = true
}
It's also good practice to remove observers in the deinit
method:
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillHide, object: nil)
Note that currentScrollViewOffset
and activeTextFieldMaxYOnScreen
are instance properties of the view controller. To get the activeTextFieldMaxYOnScreen
value, you can get it from the text field delegate:
// text field should begin editing
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
// convert text field line's max-y to perceived max-y on the screen
let line = textField.viewWithTag(123)
activeTextFieldMaxYOnScreen = (line?.convert((line?.bounds.origin)!, to: nil).y)! + (line?.frame.height)!
return true
}