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

iOS开发:TextKit 2中UITextView滚动到指定文本位置的问题

TextKit 2下UITextView滚动到指定文本的解决方案

问题根源

你遇到的问题确实是TextKit 2的懒加载优化导致的——未进入可视区域的文本片段不会提前执行布局计算,因此对应的layoutFragmentFrame为空,layoutState为None,无法获取有效位置信息。

修正后的实现代码

private func scroll(in textview: UITextView, to substring: String) {
    guard let text = textview.attributedText else { return }
    let str = text.string as NSString
    let substringRange = str.range(of: substring)
    
    guard substringRange.location != NSNotFound else { return }
    
    if #available(iOS 16, *),
       let textLayoutManager = textview.textLayoutManager,
       let textContentManager = textLayoutManager.textContentManager {
        
        // 创建目标文本对应的NSTextRange
        guard let startLocation = textContentManager.location(textContentManager.documentRange.location, offsetBy: substringRange.location),
              let endLocation = textContentManager.location(startLocation, offsetBy: substringRange.length),
              let targetRange = NSTextRange(location: startLocation, end: endLocation) else {
            return
        }
        
        // 强制布局管理器计算目标范围的布局,解决懒加载问题
        textLayoutManager.enumerateTextLayoutFragments(from: startLocation, options: .ensuresLayout) { _, stop in
            stop.pointee = true // 找到目标片段后停止遍历
        }
        
        // 获取目标范围在文本容器中的实际矩形
        guard let textContainer = textview.textContainer,
              let targetRect = textLayoutManager.boundingRect(for: targetRange, in: textContainer) else {
            return
        }
        
        // 转换坐标系并计算正确的滚动偏移量
        let convertedRect = textview.convert(targetRect, from: textLayoutManager.textContainerCoordinateSpace)
        let contentOffset = textview.contentOffsetForRect(convertedRect, animated: true)
        
        textview.setContentOffset(contentOffset, animated: true)
        
    } else {
        // iOS 16以下兼容TextKit 1的实现
        textview.scrollRangeToVisible(substringRange)
    }
}

关键优化点

  • 强制触发布局:通过enumerateTextLayoutFragments(from:options:)传入.ensuresLayout选项,强制TextKit 2提前计算目标位置所在文本片段的布局,确保能获取到有效的位置信息。
  • 可靠获取矩形范围:使用boundingRect(for:in:)直接获取目标文本范围对应的矩形区域,比单独获取布局片段的frame更稳定。
  • 坐标系转换:将文本容器坐标系下的矩形转换为UITextView的坐标系,再通过contentOffsetForRect计算偏移量,自动适配文本容器内边距和滚动视图的contentInset。

额外注意事项

  • 如果substring在文本中存在多个匹配,range(of:)只会返回第一个匹配的范围,若需处理多个匹配,需遍历所有匹配结果。
  • 若需要滚动到目标文本的顶部而非整个范围的位置,可以直接使用convertedRect.origin.y - textview.contentInset.top作为y轴偏移量。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 10:00:19