view.keyboardLayoutGuide.topAnchor using Auto Layout. For older iOS versions or manual layout, observe UIResponder.keyboardWillShowNotification and UIResponder.keyboardWillHideNotification to animate your view’s frame or layout constraints using the keyboard height from userInfo.Why this happens
When a user taps into a UITextField or UITextView located in the lower half of the screen, iOS displays the software keyboard as an overlay directly above the current window. In standard UIKit view hierarchies, the view controller’s root view does not automatically adjust its position or height when the keyboard appears.
Because the keyboard renders in its own top-level window (UIRemoteKeyboardWindow), it does not push the underlying view hierarchy upward. The input element retains its original screen coordinates, resulting in the keyboard obscuring the text field and preventing the user from seeing what they type.
Resolving this requires either anchoring your views to Apple’s built-in keyboard layout guide or intercepting system keyboard notifications to update Auto Layout constraints or frame offsets dynamically.
How to fix it
1. Constrain views to keyboardLayoutGuide (iOS 15+)
Starting in iOS 15, UIKit provides UIKeyboardLayoutGuide. This is the cleanest and most reliable way to position views above the keyboard because it automatically tracks keyboard height, docking status, hardware keyboards, and dismissal animations without manual notification handling.
Anchor the bottom of your text field or containing stack view to view.keyboardLayoutGuide.topAnchor:
import UIKit
class LoginViewController: UIViewController {
private let textField: UITextField = {
let field = UITextField()
field.placeholder = "Enter password"
field.borderStyle = .roundedRect
field.translatesAutoresizingMaskIntoConstraints = false
return field
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
view.addSubview(textField)
NSLayoutConstraint.activate([
textField.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20),
textField.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20),
// Pin the bottom of the field to the top of the keyboard guide
textField.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor, constant: -16)
])
}
}
When the keyboard is dismissed, view.keyboardLayoutGuide.topAnchor aligns with the bottom of the screen (or the bottom safe area). When the keyboard opens, the anchor tracks the keyboard frame in real time, animating your text field upward automatically.
2. Observe keyboard notifications and update constraints
If you need backward compatibility with iOS 14 or want fine-grained programmatic control over the animation, observe system notifications via NotificationCenter. Instead of shifting the entire view.frame.origin.y (which can break navigation bars and modal presentations), update a bottom Auto Layout constraint.
First, retain a reference to your bottom constraint:
import UIKit
class DynamicFormViewController: UIViewController {
private var bottomConstraint: NSLayoutConstraint?
private let submitButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Submit", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
view.addSubview(submitButton)
let bottom = submitButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16)
self.bottomConstraint = bottom
NSLayoutConstraint.activate([
submitButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
bottom
])
setupKeyboardObservers()
}
private func setupKeyboardObservers() {
NotificationCenter.default.addObserver(
self,
selector: #selector(handleKeyboardNotification),
name: UIResponder.keyboardWillShowNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(handleKeyboardNotification),
name: UIResponder.keyboardWillHideNotification,
object: nil
)
}
@objc private func handleKeyboardNotification(notification: Notification) {
guard let userInfo = notification.userInfo,
let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect,
let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double,
let curveValue = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt else {
return
}
let isHiding = notification.name == UIResponder.keyboardWillHideNotification
let bottomOffset: CGFloat
if isHiding {
bottomOffset = -16
} else {
// Subtract safe area inset so the view does not double-count the bottom inset
let safeAreaBottom = view.safeAreaInsets.bottom
bottomOffset = -(keyboardFrame.height - safeAreaBottom + 16)
}
bottomConstraint?.constant = bottomOffset
let options = UIView.AnimationOptions(rawValue: curveValue << 16)
UIView.animate(withDuration: duration, delay: 0, options: options) {
self.view.layoutIfNeeded()
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
3. Adjust UIScrollView content insets for long forms
When a screen contains multiple text fields, shifting the entire view often causes top elements (like headers, navigation bars, or upper fields) to slide out of sight. The standard Apple HIG solution is embedding all inputs inside a UIScrollView and adjusting its contentInset.
import UIKit
class ScrollFormViewController: UIViewController {
private let scrollView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
setupLayout()
registerForKeyboardEvents()
}
private func setupLayout() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
private func registerForKeyboardEvents() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
@objc private func keyboardWillChangeFrame(notification: Notification) {
guard let userInfo = notification.userInfo,
let keyboardEndFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else {
return
}
let keyboardFrameInView = view.convert(keyboardEndFrame, from: nil)
let isDismissed = keyboardFrameInView.origin.y >= view.bounds.height
if isDismissed {
scrollView.contentInset = .zero
scrollView.verticalScrollIndicatorInsets = .zero
} else {
let coveredHeight = view.bounds.height - keyboardFrameInView.origin.y
let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: coveredHeight, right: 0)
scrollView.contentInset = contentInsets
scrollView.verticalScrollIndicatorInsets = contentInsets
}
}
}
This approach ensures that every text field remains accessible via scrolling without clipping navigation bars or search headers.
How to prevent it
- Prefer
view.keyboardLayoutGuidefor single-field screens or bottom toolbars on iOS 15 and above. - Embed forms and multi-input interfaces in a
UIScrollViewfrom the start so insets can expand as needed. - Convert coordinate spaces with
view.convert(_:from:)when calculating keyboard overlaps, ensuring compatibility with iPad multitasking and split-screen modes. - Account for
view.safeAreaInsets.bottomwhen computing height offsets so your view does not jump higher than the actual keyboard frame. - Always extract the animation curve and duration from
notification.userInfoto synchronize your view transition with the keyboard slide.
FAQ
Why does altering self.view.frame.origin.y mess up my navigation bar?
Modifying the root view’s frame origin shifts the entire coordinate space upward, often pushing UINavigationBar off the top of the window and creating blank spaces under presentation contexts. Modifying Auto Layout constraints or scroll view insets keeps the root view intact.
How do I dismiss the keyboard when tapping outside the text field?
Attach a UITapGestureRecognizer to your root view with cancelsTouchesInView = false and invoke view.endEditing(true) in the action selector.
Does SwiftUI suffer from this same issue?
SwiftUI automatically accounts for the keyboard within Form, List, and standard layouts by adjusting safe area insets. If you need a view to ignore the keyboard in SwiftUI, you must explicitly declare .ignoresSafeArea(.keyboard, edges: .bottom).
Parts of this guide are adapted from content licensed under CC BY-SA 3.0; this article is shared under the same license.
