实现iMessages式交互式键盘,如何持续获取键盘实时Frame?
获取iOS键盘实时Frame的最佳方案
我正好做过类似的需求,你提到的那些系统通知确实只能在键盘显示/隐藏、完成位置调整这类关键节点触发,没法在滑动过程中持续返回精确的Frame。要实现类似iMessages里的实时追踪,这里有几个经过验证的靠谱方案:
方案一:用CADisplayLink实时查询键盘Frame
这是最直接且无私有API风险的方式,利用屏幕刷新周期来持续获取键盘的当前Frame:
- 创建一个
CADisplayLink实例,绑定到主RunLoop - 在每帧刷新的回调里,定位到键盘所在的窗口并获取其Frame
- 记得在不需要追踪时及时暂停并释放,避免不必要的性能消耗
示例代码:
private var displayLink: CADisplayLink? func startTrackingKeyboard() { displayLink = CADisplayLink(target: self, selector: #selector(updateKeyboardFrame)) displayLink?.add(to: .main, forMode: .common) } func stopTrackingKeyboard() { displayLink?.invalidate() displayLink = nil } @objc private func updateKeyboardFrame() { // 定位键盘窗口:通常是当前的keyWindow且origin.y小于屏幕高度 guard let keyboardWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow && $0.frame.origin.y < UIScreen.main.bounds.height }) else { return } let currentKeyboardFrame = keyboardWindow.frame // 在这里处理你的UI逻辑,比如调整输入框位置 }
这个方案的性能开销极低,因为CADisplayLink是跟屏幕刷新率同步的,只会在需要的时候执行。
方案二:iOS 16+用UIKeyboardObservation异步序列
苹果在iOS 16引入了UIKeyboardObservation,这是官方推荐的异步订阅方式,能自动推送键盘的所有变化——包括滑动调整位置时的实时更新:
func startTrackingKeyboard() async { for await change in UIKeyboardObservation.default.observations { let currentFrame = change.frameEnd // 这里拿到的就是实时的键盘Frame // 处理你的交互逻辑 } }
这个方案完全符合现代Swift异步编程的风格,不需要自己管理刷新周期,系统会自动处理所有状态变化的推送,稳定性拉满。
方案三:监听键盘窗口的Frame变化(不推荐)
键盘本质是在一个独立的UIWindow中显示的,我们可以通过KVO监听这个窗口的frame属性来获取实时变化。不过要注意,这个方案依赖私有类名,有兼容性风险:
private var keyboardWindow: UIWindow? private var frameObservation: NSKeyValueObservation? func startTrackingKeyboard() { // 键盘窗口的私有类名是UIRemoteKeyboardWindow,苹果未来可能修改 keyboardWindow = UIApplication.shared.windows.first(where: { $0.className == "UIRemoteKeyboardWindow" }) frameObservation = keyboardWindow?.observe(\.frame, options: [.new]) { [weak self] _, change in guard let newFrame = change.newValue else { return } self?.handleKeyboardFrameUpdate(newFrame) } } func stopTrackingKeyboard() { frameObservation?.invalidate() frameObservation = nil keyboardWindow = nil }
除非你能接受未来iOS版本可能导致的兼容性问题,否则尽量避免用这个方案。
总结
- 若你的APP只需要支持iOS 16及以上,优先选方案二,官方API最可靠
- 要兼容更低版本的话,方案一是最稳妥的选择,没有任何风险
- 方案三尽量不用,毕竟依赖私有API,随时可能失效
内容的提问来源于stack exchange,提问作者Berk Kaya




