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) }
问题根源
- 线程用错了:
textProxy(也就是UITextDocumentProxy)是和UI相关的对象,所有操作必须在主线程执行。你把fullDocumentContext()放到后台队列readerQueue里调用,会导致textProxy的状态更新不同步,迭代逻辑自然拿不到正确的上下文。 - 靠sleep同步不靠谱:用
Thread.sleep等待textProxy更新状态完全看运气,不同设备、系统的更新速度不一样,大概率等不够时间,导致后面迭代时textProxy的上下文还没更新,自然拿不到内容。 - 光标偏移执行失效:虽然你计算的偏移量是对的,但因为线程错误,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
相关产品推荐
相关产品推荐

