You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

实现iMessages式交互式键盘,如何持续获取键盘实时Frame?

获取iOS键盘实时Frame的最佳方案

我正好做过类似的需求,你提到的那些系统通知确实只能在键盘显示/隐藏、完成位置调整这类关键节点触发,没法在滑动过程中持续返回精确的Frame。要实现类似iMessages里的实时追踪,这里有几个经过验证的靠谱方案:

方案一:用CADisplayLink实时查询键盘Frame

这是最直接且无私有API风险的方式,利用屏幕刷新周期来持续获取键盘的当前Frame:

  1. 创建一个CADisplayLink实例,绑定到主RunLoop
  2. 在每帧刷新的回调里,定位到键盘所在的窗口并获取其Frame
  3. 记得在不需要追踪时及时暂停并释放,避免不必要的性能消耗

示例代码:

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

火山引擎 最新活动