如何实现语音朗读时逐段高亮并滚动TextView?
实现分段朗读+自动高亮+滚动的完整方案
我懂你想要的效果:点击朗读按钮后,逐段高亮并朗读文本,每读完一段就自动把下一段滚动到TextView中心,循环往复。结合你给出的现有代码,我一步步告诉你需要添加的代码和修改点:
第一步:添加状态管理与分段数据存储
首先要在TestViewController里加几个属性,用来保存段落数据、当前朗读位置和段落范围(方便高亮):
// 存储拆分后的段落数组(过滤空段落) private var paragraphs: [String] = [] // 当前正在朗读的段落索引 private var currentParagraphIndex = 0 // 记录每个段落对应的文本范围,用于高亮和滚动 private var paragraphRanges: [NSRange] = []
第二步:初始化段落数据
在viewDidLoad里把TextView的文本拆成段落,同时计算每个段落的NSRange:
override func viewDidLoad() { super.viewDidLoad() synthesizer.delegate = self // 拆分文本为段落(按换行符分割,过滤掉空行) let fullText = textViewOutlet.text ?? "" paragraphs = fullText.components(separatedBy: .newlines) .filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } // 计算每个段落对应的NSRange var currentLocation = 0 for paragraph in paragraphs { let length = paragraph.count paragraphRanges.append(NSRange(location: currentLocation, length: length)) // 加上换行符的长度(最后一段不用加) currentLocation += length + (paragraph == paragraphs.last ? 0 : 1) } }
第三步:实现朗读按钮的核心逻辑
假设你的朗读按钮IBAction是startSpeech(_:),如果没有就先创建,然后实现开始/重置朗读的逻辑:
@IBAction func startSpeech(_ sender: UIButton) { // 如果正在朗读,先停止并重置状态 if synthesizer.isSpeaking { synthesizer.stopSpeaking(at: .immediate) currentParagraphIndex = 0 textViewOutlet.selectedRange = NSRange(location: 0, length: 0) return } // 从第一段开始朗读 currentParagraphIndex = 0 readCurrentParagraph() }
第四步:核心的朗读+高亮+滚动方法
写一个专门处理当前段落朗读的方法,包含高亮和滚动到中心的逻辑:
private func readCurrentParagraph() { // 读完所有段落后循环回到第一段 guard currentParagraphIndex < paragraphs.count else { currentParagraphIndex = 0 readCurrentParagraph() return } let paragraph = paragraphs[currentParagraphIndex] let utterance = AVSpeechUtterance(string: paragraph) // 可根据需求设置语音、语速 utterance.voice = AVSpeechSynthesisVoice(language: "zh-CN") utterance.rate = 0.5 // 高亮当前段落 let range = paragraphRanges[currentParagraphIndex] textViewOutlet.selectedRange = range // 滚动到当前段落的中心位置 let glyphRect = textViewOutlet.layoutManager.boundingRect(forGlyphRange: range, in: textViewOutlet.textContainer) let centerOffsetY = glyphRect.origin.y + glyphRect.height/2 - textViewOutlet.bounds.height/2 let centerRect = CGRect(x: glyphRect.origin.x, y: centerOffsetY, width: glyphRect.width, height: glyphRect.height) textViewOutlet.scrollRectToVisible(centerRect, animated: true) // 开始朗读 synthesizer.speak(utterance) }
第五步:监听朗读完成,自动切换下一段
利用AVSpeechSynthesizerDelegate的代理方法,当一段朗读结束后自动触发下一段:
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) { // 索引+1,读取下一段 currentParagraphIndex += 1 readCurrentParagraph() }
第六步:完善暂停/继续按钮逻辑
补充你现有pauseSpeech的代码,实现暂停和继续的切换:
@IBAction func pauseSpeech(_ sender: UIButton) { if synthesizer.isSpeaking { synthesizer.pauseSpeaking(at: .word) sender.setTitle("继续", for: .normal) } else if synthesizer.isPaused { synthesizer.continueSpeaking() sender.setTitle("暂停", for: .normal) } }
小提示
- 如果是英文朗读,记得把
AVSpeechSynthesisVoice的language改成"en-US" - 拆分段落的逻辑可以按需调整,比如按
\n\n(空行)分割,更符合常规排版 - 滚动到中心的计算逻辑适配了TextView的contentOffset,确保段落始终居中显示
内容的提问来源于stack exchange,提问作者dscrown
相关产品推荐
相关产品推荐

