【IQKeyboardManager】解决键盘遮挡的终极方案——1行代码,零配置
iOS三方库精读 · 第 18 期
一、一句话介绍
IQKeyboardManager 是一个用于 iOS 的键盘遮挡解决方案,它通过 Method Swizzling 自动监听键盘事件,在键盘弹出时自动滚动页面让输入框始终可见,并提供 Previous / Next / Done 工具栏,整个过程无需在任何 ViewController 中写一行代码。
| 属性 | 信息 |
|---|---|
| ⭐ GitHub Stars | 16.5k+ |
| 最新稳定版 | IQKeyboardManagerSwift 7.1.x (Swift) |
| License | MIT |
| 支持平台 | iOS 13+ |
| 语言 | Objective-C(原版)+ Swift 封装 |
二、为什么选择它
原生痛点
在 iOS 中处理键盘遮挡,原生方案至少需要:
// ❌ 手动方案(基础版本,还未处理所有 edge case)
class FormVC: UIViewController {
var keyboardHeight: CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
// 1. 注册键盘通知
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow),
name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide),
name: UIResponder.keyboardWillHideNotification, object: nil)
// 2. 点击收键盘
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tap)
}
@objc func keyboardWillShow(_ notification: Notification) {
// 3. 计算键盘高度
guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
keyboardHeight = keyboardFrame.height
// 4. 找到当前激活的 textField
// 5. 计算 textField 是否被遮挡
// 6. 调整 scrollView.contentInset 或 view.frame.origin.y
// ... 还有横竖屏、iPad、外接键盘等 edge case
}
@objc func keyboardWillHide(_ notification: Notification) {
// 7. 恢复布局
}
@objc func dismissKeyboard() { view.endEditing(true) }
deinit { NotificationCenter.default.removeObserver(self) }
}
这段代码每个有输入框的 VC 都要写一遍,且还没处理 ScrollView / UITableView / 多个输入框 Tab 切换等场景。
IQKeyboardManager 只需:
// AppDelegate.swift
IQKeyboardManager.shared.isEnabled = true
// 全局生效,所有 VC 自动受管理
三、核心功能速览
基础层(新手必读)
Swift 集成
// SPM: https://github.com/hackiftekhar/IQKeyboardManager.git
// Product: IQKeyboardManagerSwift
# CocoaPods
pod 'IQKeyboardManagerSwift' # Swift 版
pod 'IQKeyboardManager' # OC 版(原版)
AppDelegate 配置(一次设置)
// Swift
import IQKeyboardManagerSwift
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
IQKeyboardManager.shared.isEnabled = true
return true
}
// Objective-C
#import <IQKeyboardManager/IQKeyboardManager.h>
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)options {
[IQKeyboardManager sharedManager].enable = YES;
return YES;
}
进阶层(最佳实践)
常用配置项
let iq = IQKeyboardManager.shared
iq.isEnabled = true
// ---- 键盘距离 ----
iq.keyboardDistanceFromTextField = 15 // 键盘顶部与输入框底部的最小间距(默认10)
// ---- 收键盘 ----
iq.resignOnTouchOutside = true // 点击空白处收键盘
// ---- ToolBar 配置 ----
iq.enableAutoToolbar = true // 是否自动添加 Toolbar(默认true)
iq.toolbarConfiguration.manageBehaviourBy = .subviews // 按视图层级排序
// 或
iq.toolbarConfiguration.manageBehaviourBy = .tag // 按 tag 值排序(推荐表单)
iq.toolbarConfiguration.toolbarTintColor = .systemBlue // Toolbar 按钮色
iq.toolbarConfiguration.placeholderConfiguration.showPlaceholder = true // 显示 placeholder
// ---- 禁用某个 VC ----
iq.disabledDistanceHandlingClasses.append(ChatViewController.self)
iq.disabledToolbarClasses.append(ChatViewController.self)
iq.disabledTouchResignedClasses.append(ChatViewController.self)
UITextField 的 tag 排序
当表单有多个输入框时,设置 tag 值控制 Previous/Next 的跳转顺序:
nameField.tag = 1
emailField.tag = 2
phoneField.tag = 3
passwordField.tag = 4
// IQKeyboardManager 会按 tag 升序排列 Toolbar 的 Previous/Next 跳转
iq.toolbarConfiguration.manageBehaviourBy = .tag
对特定 TextField 单独配置
// 某个 TextField 不希望 IQ 管理
searchBar.keyboardDistanceFromTextField = CGFloat.greatestFiniteMagnitude
// 某个 TextView 自定义 Done 按钮文字
bioTextView.keyboardToolbar.doneBarButton.title = "关闭键盘"
临时禁用(特定页面)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
IQKeyboardManager.shared.isEnabled = false // 当前页面禁用
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
IQKeyboardManager.shared.isEnabled = true // 离开后恢复
}
深入层(源码视角)
Method Swizzling 的工作机制
IQKeyboardManager 通过 Swizzling 注入了以下系统方法:
UIViewController.viewWillAppear:/viewWillDisappear:→ 追踪当前 VCUITextField.becomeFirstResponder/resignFirstResponder→ 追踪激活的 textField
当键盘弹出时:
- 通过 UIApplication 遍历 view 层级找到当前 firstResponder
- 将 firstResponder 的 frame 转换到 window 坐标系
- 与键盘 frame 对比,计算需要上移的距离
- 通过修改
UIScrollView.contentOffset或UIViewController.view.frame.origin.y实现避让
为什么不是所有场景都适用
IQKeyboardManager 的自动处理对 99% 的标准表单场景完美适用,但在以下场景需要手动处理:
- 聊天气泡列表:需要精确控制 UICollectionView 的 contentOffset,IQ 的计算会冲突
- 全屏文本编辑器:inputAccessoryView 固定在键盘上方,不需要额外偏移
- 自定义底部 Sheet:键盘弹出时 Sheet 整体上移,IQ 会双重处理
四、实战演示
场景:多字段注册表单(最典型应用)
import IQKeyboardManagerSwift
// AppDelegate(全局一次)
IQKeyboardManager.shared.isEnabled = true
IQKeyboardManager.shared.resignOnTouchOutside = true
IQKeyboardManager.shared.toolbarConfiguration.manageBehaviourBy = .tag
// RegisterFormVC(无需任何键盘处理代码!)
class RegisterFormVC: UIViewController {
private let scrollView = UIScrollView()
// 各字段自动得到 Previous/Next/Done 工具栏
private let usernameField = { $0.tag = 1; return $0 }(UITextField())
private let emailField = { $0.tag = 2; return $0 }(UITextField())
private let passwordField = { $0.tag = 3; $0.isSecureTextEntry = true; return $0 }(UITextField())
private let phoneField = { $0.tag = 4; $0.keyboardType = .phonePad; return $0 }(UITextField())
// 完全无需写任何键盘避让代码!
override func viewDidLoad() {
super.viewDidLoad()
setupScrollView()
// 不需要注册任何 Notification
// 不需要实现 UITextFieldDelegate 的 shouldReturn
// 不需要添加 TapGestureRecognizer
}
}
五、源码亮点
进阶层:单例管理器模式
IQKeyboardManager 是一个全局单例,通过统一的单例入口管理所有状态。这带来了极简的使用体验,但也意味着:
- 配置是全局的,需要注意在不同 VC 间的状态隔离
- 禁用某个 VC 时必须在
viewWillAppear/viewWillDisappear配对处理
深入层:为什么不用 UIScrollView.keyboardDismissMode
苹果在 iOS 7 引入了 UIScrollView.keyboardDismissMode = .onDrag,但它只处理"收键盘",不处理"避让键盘遮挡"。IQKeyboardManager 解决的是更完整的键盘管理问题。
iOS 16+ 苹果引入了 .scrollDismissesKeyboard modifier(SwiftUI),但 UIKit 仍需手动处理避让。
六、踩坑记录
问题 1:键盘弹出后整个 view 偏移过大
- 原因:
keyboardDistanceFromTextField设置值过大,或 ScrollView 的 contentInset 被重复计算 - 解决:调小
keyboardDistanceFromTextField;检查是否同时存在手动 notification 处理
问题 2:SwiftUI 的 TextField 不受 IQKeyboardManager 管理
- 原因:IQKeyboardManager 针对 UIKit 的 UITextField/UITextView,SwiftUI 的 TextField 底层实现不同
- 解决:SwiftUI 用
.scrollDismissesKeyboard(.interactively)(iOS 16+)或手动处理
问题 3:某个 VC 禁用后忘记恢复,导致其他页面也失效
- 原因:在
viewWillAppear禁用,但在viewWillDisappear忘记恢复 - 解决:配对处理,或使用
disabledDistanceHandlingClasses一次性配置
问题 4:Toolbar 上的 Done 按钮没有收起键盘
- 原因:某些 VC 的输入框在自定义 container 里,IQ 无法识别层级
- 解决:手动设置
textField.keyboardToolbar.doneBarButton.target = textField; doneBarButton.action = #selector(UIResponder.resignFirstResponder)
问题 5:iPad 上 popover 中的 TextField 行为异常
- 原因:iPad 的键盘行为与 iPhone 不同(浮动键盘)
- 解决:对 iPad popover 场景禁用 IQ,手动处理
问题 6:与 UISearchController 冲突
- 原因:UISearchController 的 searchBar 不走标准的 UIViewController 生命周期
- 解决:在
disabledDistanceHandlingClasses中排除相关 VC,或通过enabledDistanceHandlingClasses精确指定受管理的 VC
七、延伸思考
iOS 16+ 的原生键盘管理
苹果在 iOS 16 引入了更多原生键盘支持:
// SwiftUI - iOS 16+
ScrollView {
// ...
}
.scrollDismissesKeyboard(.interactively) // 拖拽时收键盘
// UIKit - iOS 16+
textView.keyboardDismissMode = .interactiveWithAccessory
但对于 UIKit 的"避让遮挡"问题,iOS 17 之前仍无原生解决方案,IQKeyboardManager 仍然是最佳选择。
推荐使用场景
- ✅ 包含多个输入框的表单页面(注册、设置、地址填写)
- ✅ 需要快速开发,不想写键盘处理代码
- ✅ OC 和 Swift 混合项目
- ✅ 需要 Previous/Next 快速切换输入框
不推荐场景
- ❌ 聊天界面(需要精确控制键盘交互)
- ❌ 全屏文本编辑器(inputAccessoryView 方案更合适)
- ❌ 纯 SwiftUI 项目(使用 SwiftUI 原生方案)
八、参考资源
- GitHub: hackiftekhar/IQKeyboardManager
- IQKeyboardManager 官方文档
- Apple 键盘通知文档
- iOS 16 UIKeyboardLayoutGuide
- 系列 Demo 仓库:
github.com/yourname/ios-lib-demos
九、本期互动
小作业
在一个包含 ScrollView 的注册表单页(6个字段:用户名/邮箱/密码/确认密码/手机/验证码)中,对比两种实现:① 手动监听 UIKeyboardWillShowNotification 处理避让,② 使用 IQKeyboardManager 1行配置。统计两种方式的代码行数,并在评论区分享你手动方案中遇到的 edge case。
思考题
IQKeyboardManager 用 Method Swizzling 注入系统方法来实现"0侵入"的键盘管理,这是一个权衡:使用方便但增加了黑盒复杂度。苹果在 iOS 16 引入了 UIKeyboardLayoutGuide 作为原生解决方案。你认为三方库的"方便性"和"原生 API 的可控性"之间的边界在哪里?何时该用三方库,何时该坚持原生?
读者征集
下一期我们将深入 GRDB.swift(Swift 中最好的 SQLite 封装)。你在项目中选择过 GRDB / SQLite.swift / Realm / CoreData 吗?做数据库选型时你最看重哪个维度(性能/API 简洁度/类型安全/OC 兼容性)?欢迎评论区分享。
📅 本系列每周五晚更新 ✅ 第14期:Moya · ✅ 第15期:SwiftyJSON · ✅ 第16期:R.swift · ✅ 第17期:PromiseKit · ➡️ 第18期:IQKeyboardManager · ○ 第19期:GRDB.swift