UITableView设置较大bottom contentInset后键盘弹出时滚动位置异常
问题原因
UITableView 继承自 UIScrollView,系统内置了默认行为:当滚动视图内部的 UITextField / UITextView 成为第一响应者唤起键盘时,会自动计算输入控件在窗口中的可见位置,如果判断输入控件会被键盘遮挡、且滚动视图有足够的滚动余量,就会自动调整 contentOffset 将输入控件滚动到可见区域。
你设置了较大的 contentInset.bottom 后,滚动视图的可滚动范围(contentSize.height + 上下contentInset总和)远大于自身 frame 高度,系统判定有足够的滚动空间,就触发了自动滚动逻辑,这就是异常现象的成因。
解决方案(不使用
contentInsetAdjustmentBehavior = .never 前提下) 方案1:轻量拦截系统自动滚动
监听键盘弹出通知,在系统执行默认滚动逻辑后强制还原当前contentOffset,覆盖系统的自动滚动操作:
override func viewDidLoad() { super.viewDidLoad() // 原有逻辑保持不变 // 添加键盘监听 NotificationCenter.default.addObserver(self, selector: #selector(lockTableOffset), name: UIResponder.keyboardWillShowNotification, object: nil) } @objc private func lockTableOffset() { let currentOffset = tableView.contentOffset // 异步操作会在系统默认滚动逻辑执行后触发,直接重置offset DispatchQueue.main.async { [weak self] in self?.tableView.setContentOffset(currentOffset, animated: false) } } // 移除通知避免内存泄漏 deinit { NotificationCenter.default.removeObserver(self) }
方案2:精准控制滚动拦截
如果需要区分用户手动滚动和系统自动滚动,避免影响正常交互,可以自定义UITableView子类实现拦截:
// 自定义TableView类 class NoAutoScrollTableView: UITableView { // 标记是否禁止系统自动滚动 var disableSystemAutoScroll = false override func setContentOffset(_ contentOffset: CGPoint, animated: Bool) { // 仅拦截非用户主动触发的滚动,用户手动滑动、甩动不受影响 if disableSystemAutoScroll && !isTracking && !isDragging && !isDecelerating { super.setContentOffset(self.contentOffset, animated: false) return } super.setContentOffset(contentOffset, animated: animated) } }
在控制器中配合输入框代理控制标记位即可:
// 替换原有tableView的类型 let tableView = NoAutoScrollTableView() // 在cellForRowAt方法中给textField设置代理 textField.delegate = self // 实现UITextFieldDelegate协议 extension TestViewController: UITextFieldDelegate { func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { tableView.disableSystemAutoScroll = true return true } func textFieldDidEndEditing(_ textField: UITextField) { tableView.disableSystemAutoScroll = false } }
内容的提问来源于stack exchange,提问作者yunhao
相关产品推荐
相关产品推荐

