swiftcombinesfspeechrecognizer

Publishing and Consuming a transcript from SFSpeechRecognizer


I'm using Apple's example of an Observable wrapper around SFSpeechRecognizer as follows:

class SpeechRecognizer: ObservableObject {
    @Published var transcript: String
    func transcribe() {}
}

The goal is to use a ViewModel to both consume the transcript as it is generated, as well as passing on the value to a SwiftUI View for visual debugging:

class ViewModel : ObservableObject {
    @Published var SpeechText: String = ""
    @ObservedObject var speech: SpeechRecognizer = SpeechRecognizer()

    public init() {
        speech.transcribe()
        speech.transcript.publisher
            .map { $0 as! String? ?? "" }
            .sink(receiveCompletion: {
                print ($0) },
                  receiveValue: {
                    self.SpeechText = $0
                    self.doStuff(transcript: $0)
                  })
    }

    private void doStuffWithText(transcript: String) {
        //Process the output as commands in the application
    }
}

I can confirm that if I observe transcript directly in a SwiftUI view, that the data is flowing through. My problem is receiving the values as they change, and then assigning that data to my own published variable.

How do I make this work?


Solution

  • Subscription should be stored otherwise it is canceled immediately, also you need to make subscription before actual usage (and some other memory related modifications made). So I assume you wanted something like:

    class ViewModel : ObservableObject {
        @Published var SpeechText: String = ""
        var speech: SpeechRecognizer = SpeechRecognizer()  // << here !!
    
        private var subscription: AnyCancellable? = nil    // << here !!
        public init() {
            self.subscription = speech.transcript.publisher  // << here !!
                .map { $0 as! String? ?? "" }
                .sink(receiveCompletion: {
                    print ($0) },
                      receiveValue: { [weak self] value in
                        self?.SpeechText = value
                        self?.doStuffWithText(transcript: value)
                      })
            self.speech.transcribe()                  // << here !!
        }
    
        private func doStuffWithText(transcript: String) {
            //Process the output as commands in the application
        }
    }
    

    Tested with Xcode 13.2