You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
    }
}

使用步骤

  1. 在你的ViewController的viewDidLoad方法里,给需要适配的UITextView初始化避让逻辑:
setupKeyboardAvoidance(for: yourTextView)
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:22:25