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
相关产品推荐
相关产品推荐

