You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift自定义键盘(Keyboard Kit)光标在文本中间时获取完整上下文异常

自定义键盘获取完整文本上下文的迭代问题排查

基于Keyboard Kit框架开发Swift自定义键盘,适配Swift 5后碰到个问题:只有光标在文本末尾时,能正常拿到完整的文本上下文;要是光标在文本中间,获取光标前内容再移回光标,之后的向前迭代就只能成功一次。

相关代码如下:

let readerQueue = DispatchQueue(label: "com.customKeyboard.reader.test")

func foo() {
    readerQueue.async {
        self.keyboardService.fullDocumentContext()
    }
}

func fullDocumentContext() {
    guard let textProxy else { return }
    var startingOffset = 0
    var context = ""
    
    while let previeosContext = textProxy.documentContextBeforeInput, !previeosContext.isEmpty {
        context = previeosContext + context
        startingOffset += previeosContext.count
        textProxy.adjustTextPosition(byCharacterOffset: -previeosContext.count)
        Thread.sleep(forTimeInterval: 0.01)
    }
    
    textProxy.adjustTextPosition(byCharacterOffset: context.count)
    Thread.sleep(forTimeInterval: 0.01)
    
    while let nextContext = textProxy.documentContextAfterInput, !nextContext.isEmpty {
        context += nextContext
        textProxy.adjustTextPosition(byCharacterOffset: nextContext.count)
        Thread.sleep(forTimeInterval: 0.01)
    }
    
    print(context)
}

问题根源

  1. 线程用错了:textProxy(也就是UITextDocumentProxy)是和UI相关的对象,所有操作必须在主线程执行。你把fullDocumentContext()放到后台队列readerQueue里调用,会导致textProxy的状态更新不同步,迭代逻辑自然拿不到正确的上下文。
  2. 靠sleep同步不靠谱:用Thread.sleep等待textProxy更新状态完全看运气,不同设备、系统的更新速度不一样,大概率等不够时间,导致后面迭代时textProxy的上下文还没更新,自然拿不到内容。
  3. 光标偏移执行失效:虽然你计算的偏移量是对的,但因为线程错误,textProxy根本没正确执行偏移操作,后续向后迭代的起点错误,结果自然不符合预期。

解决办法

1. 把textProxy操作移到主线程

直接将fullDocumentContext()的调用切到主线程,确保所有操作在正确的线程执行:

func foo() {
    DispatchQueue.main.async {
        self.keyboardService.fullDocumentContext()
    }
}

2. 替换sleep为可靠的等待方式

用RunLoop小步等待确保textProxy状态更新,优化后的fullDocumentContext()实现:

func fullDocumentContext() {
    guard let textProxy = self.textProxy else { return }
    var fullContext = ""
    let initialCursorPos = textProxy.selectedRange.location
    
    // 向前遍历获取所有文本
    var moveOffset = 0
    while true {
        guard let beforeText = textProxy.documentContextBeforeInput, !beforeText.isEmpty else { break }
        fullContext = beforeText + fullContext
        moveOffset += beforeText.count
        textProxy.adjustTextPosition(byCharacterOffset: -beforeText.count)
        // 让RunLoop处理事件,确保textProxy状态更新
        RunLoop.current.run(until: Date().addingTimeInterval(0.005))
    }
    
    // 回到初始光标位置
    textProxy.adjustTextPosition(byCharacterOffset: moveOffset)
    RunLoop.current.run(until: Date().addingTimeInterval(0.005))
    
    // 向后遍历获取所有文本
    while let afterText = textProxy.documentContextAfterInput, !afterText.isEmpty {
        fullContext += afterText
        textProxy.adjustTextPosition(byCharacterOffset: afterText.count)
        RunLoop.current.run(until: Date().addingTimeInterval(0.005))
    }
    
    // 恢复用户原来的光标位置
    textProxy.setSelectedRange(NSRange(location: initialCursorPos, length: 0))
    
    print(fullContext)
}

3. 额外优化:恢复初始光标位置

操作前记录用户原光标位置,操作完成后恢复,避免打乱用户的输入体验。

内容的提问来源于stack exchange,提问作者excE

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 13:18:16