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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 00:05:14