UIScrollView设置contentInset适配键盘失效问题排查
看来你遇到了iOS ScrollView和键盘适配的经典坑——系统自动调整contentInset的逻辑和你手动设置的代码撞车了!导致你设置的键盘高度内缩被抵消,加小数值根本看不到效果。下面是具体的排查点和解决办法:
1. 禁用系统自动调整contentInset(最核心的修复)
iOS 11及以后,UIScrollView默认开启了contentInsetAdjustmentBehavior = .automatic,这个特性会让系统根据安全区域、键盘弹出等场景自动调整scrollView的内边距。这就意味着你在keyboardWillShow里手动设置的contentInsets.bottom,会被系统的自动调整覆盖或者抵消——这就是为什么加30这种小数值没变化,只有加很大的数值才能看出差异。
解决办法:
在viewDidLoad里给scrollView加上这行代码,彻底禁用系统的自动调整:
scrollView.contentInsetAdjustmentBehavior = .never
这样你手动设置的内边距就会完全生效,不会被系统干扰。
2. 正确恢复原始内边距,别直接设为0
你的键盘隐藏逻辑里直接把contentInsets.bottom设为0,但如果scrollView原本就有底部内边距(比如iPhone X及以后机型的安全区域内边距),这样会导致正常状态下的布局异常。建议先保存原始的内边距,在键盘隐藏时恢复:
首先在ViewController里加一个属性保存原始值:
private var originalScrollInset: UIEdgeInsets!
然后在viewDidLoad里初始化:
override func viewDidLoad() { super.viewDidLoad() originalScrollInset = scrollView.contentInset // 加上禁用自动调整的代码 scrollView.contentInsetAdjustmentBehavior = .never // 注册键盘通知 registerForKeyboardWillShowNotification(self.scrollView) registerForKeyboardWillHideNotification(self.scrollView) }
接着修改你的扩展方法(或者直接在ViewController里处理),基于原始内边距来设置:
extension UIViewController { func registerForKeyboardWillShowNotification(_ scrollView: UIScrollView, usingBlock block: ((CGSize?) -> Void)? = nil) { _ = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: nil) { [weak self] notification in guard let self = self else { return } let userInfo = notification.userInfo! let keyboardSize = (userInfo[UIResponder.keyboardFrameEndUserInfoKey]! as AnyObject).cgRectValue.size // 保留原始的顶部、左右内边距,只修改底部 let contentInsets = UIEdgeInsets( top: self.originalScrollInset.top, left: self.originalScrollInset.left, bottom: keyboardSize.height + self.originalScrollInset.bottom, right: self.originalScrollInset.right ) scrollView.contentInset = contentInsets // 同步调整滚动条的内边距,让滚动条和内容位置匹配 scrollView.scrollIndicatorInsets = contentInsets block?(keyboardSize) } } func registerForKeyboardWillHideNotification(_ scrollView: UIScrollView, usingBlock block: ((CGSize?) -> Void)? = nil) { _ = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: nil) { [weak self] notification in guard let self = self else { return } // 直接恢复到原始内边距 scrollView.contentInset = self.originalScrollInset scrollView.scrollIndicatorInsets = self.originalScrollInset let userInfo = notification.userInfo! let keyboardSize = (userInfo[UIResponder.keyboardFrameEndUserInfoKey]! as AnyObject).cgRectValue.size block?(keyboardSize) } } }
3. 确认ScrollView子视图的约束逻辑
虽然你的约束看起来没问题,但在iOS 11+中,ScrollView提供了contentLayoutGuide和frameLayoutGuide两个布局参考,确保子视图约束到正确的guide很重要:
- 把ScrollView自身的约束绑定到
frameLayoutGuide(比如和NavigationBar底部、父视图底部的约束) - 把内部的UIView(3)的top/bottom约束绑定到ScrollView的
contentLayoutGuide,而不是ScrollView自身的frame
这样能确保ScrollView的contentSize完全由内部UIView的高度决定,避免布局计算异常导致内缩效果不对。
4. 避开废弃API
如果你还在支持iOS 11以前的版本,注意automaticallyAdjustsScrollViewInsets这个属性已经被废弃,统一用contentInsetAdjustmentBehavior替代,避免新旧逻辑冲突。
按照上面的步骤调整后,键盘弹出时ScrollView的内缩效果应该就完全符合你的预期了!
内容的提问来源于stack exchange,提问作者Jeroen

