iOS 11下iPhone 6及更早机型UITextView键盘上移失效求助
解决UITextView在旧iPhone机型上无法随键盘上移的问题
咱们先捋捋你现有代码可能踩的坑:旧机型(比如iPhone6/5s)没有安全区域(Safe Area),如果你的上移逻辑依赖了safeAreaInsets.bottom,那在这些设备上肯定会计算出错;另外,要是键盘通知的frame没做坐标系转换、或者动画参数没跟键盘完全同步,也会导致看起来“没反应”。
下面给你一套兼容新旧机型的通用键盘适配方案,不管是老设备还是带刘海的新设备都能正常工作:
方案一:基于键盘通知的通用适配(推荐)
这套方案会正确处理键盘的frame变化,适配不同机型布局,还能保证动画和键盘完全同步。
完整代码实现
import UIKit extension UIViewController { private struct AssociatedKeys { static var keyboardAvoidanceConstraint: NSLayoutConstraint? } // 初始化键盘避让约束:需要提前给UITextView设置好底部基础约束,这里用关联对象保存以便动态修改 func setupKeyboardAvoidance(for textView: UITextView) { // 创建并激活textView的底部约束,优先级设为高优先级 let bottomConstraint = textView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0) bottomConstraint.priority = .defaultHigh bottomConstraint.isActive = true // 用关联对象保存约束,后续修改时调用 objc_setAssociatedObject(self, &AssociatedKeys.keyboardAvoidanceConstraint, bottomConstraint, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) // 注册键盘通知 startAvoidingKeyboard() } func startAvoidingKeyboard() { // 兼容iOS13+及更早版本的键盘通知 let notificationNames: [NSNotification.Name] = [ UIResponder.keyboardWillChangeFrameNotification, UIResponder.keyboardWillHideNotification ] for name in notificationNames { NotificationCenter.default.addObserver( self, selector: #selector(handleKeyboardChange(_:)), name: name, object: nil ) } } func stopAvoidingKeyboard() { // 移除通知监听,避免内存泄漏 NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillChangeFrameNotification, object: nil) NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) // 恢复textView的底部约束 if let constraint = objc_getAssociatedObject(self, &AssociatedKeys.keyboardAvoidanceConstraint) as? NSLayoutConstraint { constraint.constant = 0 } } @objc private func handleKeyboardChange(_ notification: Notification) { guard let userInfo = notification.userInfo else { return } // 获取键盘最终的frame、动画时长和动画曲线 let keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect) ?? .zero let animationDuration = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval) ?? 0.25 let animationCurveRawValue = (userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int) ?? UIView.AnimationCurve.easeInOut.rawValue let animationCurve = UIView.AnimationCurve(rawValue: animationCurveRawValue) ?? .easeInOut // 把键盘的屏幕坐标系frame转换为当前视图的坐标系 let convertedKeyboardFrame = view.convert(keyboardFrame, from: nil) // 计算需要上移的距离:键盘顶部到视图底部的重叠部分(键盘隐藏时为0) let keyboardOverlap = max(0, view.bounds.maxY - convertedKeyboardFrame.minY) // 获取之前保存的底部约束并更新 guard let bottomConstraint = objc_getAssociatedObject(self, &AssociatedKeys.keyboardAvoidanceConstraint) as? NSLayoutConstraint else { return } bottomConstraint.constant = keyboardOverlap // 执行和键盘同步的动画 UIView.animate(withDuration: animationDuration, delay: 0, options: UIView.AnimationOptions(rawValue: UInt(animationCurve.rawValue) << 16), animations: { self.view.layoutIfNeeded() }, completion: nil) } }
使用步骤
- 在你的ViewController的
viewDidLoad方法里,给需要适配的UITextView初始化避让逻辑:
setupKeyboardAvoidance(for: yourTextView)
- 在
viewWillDisappear里记得停止监听,避免内存泄漏:
override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) stopAvoidingKeyboard() }
方案二:结合UITextViewDelegate优化体验(可选)
如果你的UITextView是可滚动的,可以搭配代理方法,在编辑开始时自动滚动到光标位置,进一步避免遮挡:
extension YourViewController: UITextViewDelegate { func textViewDidBeginEditing(_ textView: UITextView) { // 滚动到光标所在的位置 guard let selectedRange = textView.selectedTextRange else { return } let caretRect = textView.caretRect(for: selectedRange.start) textView.scrollRectToVisible(caretRect, animated: true) } }
关键注意事项
- 别硬依赖SafeAreaInsets:旧机型没有安全区域,计算键盘高度时要用视图bounds做坐标系转换,而非直接用safeAreaInsets。
- 同步键盘动画参数:必须用键盘通知里返回的
animationDuration和animationCurve,不然动画会和键盘不同步,看起来卡顿或者没效果。 - 坐标系转换不能省:键盘的frame是基于屏幕坐标系的,一定要转成当前视图的坐标系,否则不同设备上的计算结果会出错。
内容的提问来源于stack exchange,提问作者NITESH
相关产品推荐
相关产品推荐

