1

I don't succeed to highlight text utterance in SwiftUI. I've found examples of it only in UIKit. In UIKit it is supposed to be var label: UILabel!, but in SwiftUI label has to be String. I've tried to convert NSMutableAttributedString into String format, inside function, but it's complaining. How to work it out with String formats, so that it works in SwiftUI, too?

import AVFoundation

class Speaker: NSObject {
    let synth = AVSpeechSynthesizer()
    var label: String // <- problem
    

    override init() {
        super.init()
        synth.delegate = self
    }

    func speak(_ string: String) {
        let utterance = AVSpeechUtterance(string: string)
        utterance.voice = AVSpeechSynthesisVoice(language: "en-GB")
        utterance.rate = 0.4
        synth.speak(utterance)
    }
    
    // Functions to highlight text
    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance) {
        let mutableAttributedString = NSMutableAttributedString(string: utterance.speechString)
        mutableAttributedString.addAttribute(.foregroundColor, value: UIColor.red, range: characterRange)
        label.attributedText = mutableAttributedString
    }

    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
        label.attributedText = NSAttributedString(string: utterance.speechString)
    }
}
MikeMaus
  • 385
  • 3
  • 22

1 Answers1

3

I suggest you wrap UILabel in UIViewRepresentable so that you can keep using an attributed string like before:


struct ContentView : View {
    @ObservedObject var speaker = Speaker()
    
    var body: some View {
        VStack {
            LabelRepresented(text: speaker.label)
        }.onAppear {
            speaker.speak("Hi. This is a test.")
        }
    }
}

struct LabelRepresented: UIViewRepresentable {
    var text : NSAttributedString?
    
    func makeUIView(context: Context) -> UILabel {
        return UILabel()
    }
    
    func updateUIView(_ uiView: UILabel, context: Context) {
        uiView.attributedText = text
    }
}

class Speaker: NSObject, ObservableObject, AVSpeechSynthesizerDelegate {
    let synth = AVSpeechSynthesizer()
    @Published var label: NSAttributedString? // <- change to AttributedString
    

    override init() {
        super.init()
        synth.delegate = self
    }

    func speak(_ string: String) {
        let utterance = AVSpeechUtterance(string: string)
        utterance.voice = AVSpeechSynthesisVoice(language: "en-GB")
        utterance.rate = 0.4
        synth.speak(utterance)
    }
    
    // Functions to highlight text
    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance) {
        let mutableAttributedString = NSMutableAttributedString(string: utterance.speechString)
        mutableAttributedString.addAttribute(.foregroundColor, value: UIColor.red, range: characterRange)
        label = mutableAttributedString
    }

    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
        label = NSAttributedString(string: utterance.speechString)
    }
}

I changed label into an NSAttributedString? and made it a @Published property on an ObservableObject -- that way, when it changes, the view gets notified right away.

The UIViewRepresentable creates a label and updates it with the attributed string any time it changes.

In the event that you did want to try a more pure SwiftUI approach, this blog posts has some good resources for using NSAttributedString in SwiftUI (including the approach I took): https://swiftui-lab.com/attributed-strings-with-swiftui/

jnpdx
  • 45,847
  • 6
  • 64
  • 94
  • Thanks!!! How can I align the UILablel view? With long text it spreads horizontally out of screen bounds. I've tried `.padding()` in body View, but it didn't help. – MikeMaus Feb 10 '21 at 16:27
  • 1
    Yes -- this answer details a system for doing just that: https://stackoverflow.com/questions/58469001/how-can-i-get-text-to-wrap-in-a-uilabel-via-uiviewrepresentable-without-having – jnpdx Feb 10 '21 at 16:33