SwiftUI集成UITextView实现TTS朗读时自动滚动高亮单词问题
解决方案
要解决UITextView在TTS朗读时无法自动滚动到高亮单词的问题,核心是在更新文本时让TextView滚动到当前朗读的字符范围,具体实现步骤如下:
1. 扩展VoiceManager保存朗读范围
在你的VoiceManager中新增属性存储当前朗读的字符范围:
var currentSpeakingRange: NSRange? // 新增:保存当前朗读的字符范围
然后在TTS代理方法中同步更新这个范围:
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance) { let mutableAttributedString = NSMutableAttributedString(string: utterance.speechString) mutableAttributedString.addAttribute(.backgroundColor, value: UIColor.yellow, range: characterRange) attributedText = mutableAttributedString currentSpeakingRange = characterRange // 同步保存当前朗读范围 } func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) { attributedText = NSAttributedString(string: utterance.speechString) currentSpeakingRange = nil // 朗读结束后清空范围 HomeViewState.shared.isSpeaking = false }
2. 在updateUIView中实现自动滚动
修改updateUIView方法,设置完富文本后,触发TextView滚动到当前朗读范围:
func updateUIView(_ uiView: UITextView, context: Context) { if !voiceManager.isSpeaking { let attStr = NSMutableAttributedString(string: textViewModel.text) let pStyle = NSMutableParagraphStyle() pStyle.lineSpacing = 3 attStr.addAttribute(.paragraphStyle, value: pStyle, range: NSMakeRange(0, attStr.length)) attStr.addAttribute(.font, value: UIFont(name: "TimesNewRomanPS-BoldMT", size: 18)!, range: NSMakeRange(0, attStr.length)) uiView.attributedText = attStr } else { guard let attText = voiceManager.attributedText else { return } let attStr = NSMutableAttributedString(attributedString: attText) let pStyle = NSMutableParagraphStyle() pStyle.lineSpacing = 3 attStr.addAttribute(.paragraphStyle, value: pStyle, range: NSMakeRange(0, attStr.length)) attStr.addAttribute(.font, value: UIFont(name: "TimesNewRomanPS-BoldMT", size: 18)!, range: NSMakeRange(0, attStr.length)) uiView.attributedText = attStr // 自动滚动到当前朗读的单词区域 if let speakingRange = voiceManager.currentSpeakingRange { // 基础滚动方式,自带默认动画 uiView.scrollRangeToVisible(speakingRange) // 若需要更平滑的自定义动画,可替换为以下代码: // let textRect = uiView.layoutManager.boundingRect(forGlyphRange: speakingRange, in: uiView.textContainer) // uiView.scrollRectToVisible(textRect, animated: true) } } }
关键说明
- 通过保存当前朗读的字符范围,能在每次更新TextView内容时精准定位高亮单词的位置;
scrollRangeToVisible是UITextView原生方法,可快速将指定范围滚动到可视区域;若需要更细腻的动画控制,可使用scrollRectToVisible并传入计算好的文本矩形区域;- 朗读结束后清空
currentSpeakingRange,避免后续操作触发不必要的滚动。
问题演示:
内容的提问来源于stack exchange,提问作者Zhou Haibo
相关产品推荐
相关产品推荐

