我使用 Apple 的文本转语音功能在 UITextView 中朗读文本。它是用 SwiftUI 创建的,但使用了 UIKit 中的“UITextView”,因为我需要 SwiftUI 无法提供的单词突出显示功能。
问题是,如果是超过 UITextView 限制的长文本,当 TTS 朗读单词超出最后一行时,UITextView 不会自动向下滚动以显示突出显示的单词。请参考下面的视频。
我认为我应该在
updateUIView
函数中提供口语单词范围,但不知道该怎么做。顺便说一句,voiceManager.attributedText
是从 AVSpeechSynthesizerDelegate 函数传递的,用于突出显示说话的单词。
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
textView.font = UIFont(name: "Times New Roman", size: 18)
textView.textContainerInset = .init(top: 10, left: 10, bottom: 10, right: 10)
textView.isScrollEnabled = true
textView.isEditable = true
textView.isUserInteractionEnabled = true
textView.backgroundColor = .lightGreen20
return 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 {
let attStr = NSMutableAttributedString(attributedString: voiceManager.attributedText!)
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
}
}
// MARK: Delegate functions to highlight text
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
}
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
attributedText = NSAttributedString(string: utterance.speechString)
HomeViewState.shared.isSpeaking = false
}
嗯,今天我脑子里冒出了一个想法。我设法实现了它。
// Scroll only if text exceed textView border
if uiView.contentSize.height > uiView.frame.size.height {
// Scroll textView per speaking word change
let location = voiceManager.characterRange?.location ?? 0
let yOffset = round(Double(location) / Double(textViewModel.text.count) * uiView.frame.size.height)
uiView.setContentOffset(.init(x: 0, y: yOffset), animated: true)
}
一般来说,在
updateUIView(:)
下使用characterRange / text.length
来获取文本中说出的单词的比例。然后将其与 uitextview 的高度相乘即可得到 Y 轴上的滚动偏移量。最后,将其输入到 setContentOffset(:)
函数中。它在我这边效果很好,但我想对于很长的文本来说可能不准确。以后会有更多测试。