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

iOS键盘避让扩展适配问题:可避免遮挡UITextField但对UITextView无效如何解决

问题原因
  • 原有代码仅调整了UIScrollView的contentInset底部缩进,没有主动将当前处于第一响应状态的输入控件滚动到键盘上方的可视区域。UITextField在部分系统版本下存在隐式的自动滚动适配逻辑,而UITextView没有对应默认处理,因此无法触发上移。
  • 代码中存在多处强制解包逻辑,极端场景下会触发崩溃
  • 计算底部缩进时没有扣除屏幕底部安全区高度,会导致实际偏移量偏大,出现不必要的留白
解决方案

调整键盘通知的处理逻辑,先获取当前第一响应的输入控件,计算其在ScrollView中的坐标,主动调用滚动方法将其移动到可视区域,修正后的代码如下:

extension SummaryViewController {
    /// 注册键盘frame变化通知
    func registerKeyboardNotifications() {
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(keyboardNotification(notification:)),
                                               name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
    }
    
    @objc func keyboardNotification(notification: NSNotification) {
        guard let userInfo = notification.userInfo,
              let endFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
              let duration = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue,
              let animationCurveRaw = (userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue else {
            return
        }
        let animationCurve = UIView.AnimationOptions(rawValue: animationCurveRaw)
        
        // 计算正确的bottom缩进,扣除底部安全区
        let bottomInset: CGFloat
        if endFrame.origin.y >= UIScreen.main.bounds.height {
            // 键盘收起
            bottomInset = 0
        } else {
            bottomInset = endFrame.height - view.safeAreaInsets.bottom
        }
        scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: bottomInset, right: 0)
        scrollView.scrollIndicatorInsets = scrollView.contentInset
        
        // 找到当前第一响应的输入控件,主动滚动到可视区域
        if let currentResponder = UIResponder.currentFirstResponder as? UIView,
           let responderFrameInScrollView = currentResponder.superview?.convert(currentResponder.frame, to: scrollView) {
            // 给输入框底部留10px的间距,避免紧贴键盘
            let visibleFrame = responderFrameInScrollView.insetBy(dx: 0, dy: -10)
            scrollView.scrollRectToVisible(visibleFrame, animated: true)
        }
        
        UIView.animate(withDuration: duration,
                       delay: 0,
                       options: animationCurve,
                       animations: { self.view.layoutIfNeeded() },
                       completion: nil)
    }
}

// 新增获取第一响应者的扩展,放在全局位置即可
extension UIResponder {
    private static weak var currentFirstResponderHolder: UIResponder?
    static var currentFirstResponder: UIResponder? {
        currentFirstResponderHolder = nil
        // 通过sendAction遍历找到第一响应者
        UIApplication.shared.sendAction(#selector(findFirstResponder), to: nil, from: nil, for: nil)
        return currentFirstResponderHolder
    }
    
    @objc private func findFirstResponder() {
        Self.currentFirstResponderHolder = self
    }
}
额外说明

如果你的UITextView本身开启了isScrollEnabled = true,建议将其改为false,避免嵌套滚动的冲突,让外层的UIScrollView统一处理滚动逻辑即可。

内容的提问来源于stack exchange,提问作者NoName555

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 15:06:05