iosswiftavspeechsynthesizer

AVSpeechSynthesizer detect when the speech is finished


I just don't know how to do it...

I search here and at google and people talked about the AVSpeechSynthesizerDelegate but I wasn't able to use it.

I want to run a function exactly when the speech is over.

How can I achieve this? If I must use the delegate, how should I do it?

I tried that way:

func speechSynthesizer(synthesizer: AVSpeechSynthesizer, didFinishSpeechUtterance utterance: AVSpeechUtterance) {
    falando = false
    print("FINISHED")
}

This was one of the functions I found on the developer's doc, although the speech was told and nothing was printed.

I tried to put Class A : AVSpeechSynthesizerDelegate so then I would do Speech.delegate = self (Speech is an attribute of A, of type AVSpeechSynthesizer) but it said A does not conform to protocol NSObjectProtocol.

How can I run some function (even a print) as soon as the speech is over?

Thank you!


Solution

  • A does not conform to protocol NSObjectProtocol means that your class must inherit from NSObject, you can read more about it here.

    Now I don't know how you've structured your code, but this little example seems to work for me. First a dead simple class that holds the AVSpeechSynthesizer:

    class Speaker: NSObject {
        let synth = AVSpeechSynthesizer()
    
        override init() {
            super.init()
            synth.delegate = self
        }
    
        func speak(_ string: String) {
            let utterance = AVSpeechUtterance(string: string)
            synth.speakUtterance(utterance)
        }
    }
    

    Notice that I set the delegate here (in the init method) and notice that it must inherit from NSObject to keep the compiler happy (very important!)

    And then the actual delegate method:

    extension Speaker: AVSpeechSynthesizerDelegate {
        func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
            print("all done")
        }
    }
    

    And finally, I can use that class here, like so:

    class ViewController: UIViewController {
        let speaker = Speaker()
    
        @IBAction func buttonTapped(sender: UIButton) {
            speaker.speak("Hello world")
        }
    }
    

    Which rewards me with

    all done

    in my console when the AVSpeechSynthesizer has stopped speaking.

    Hope that helps you.

    Update

    So, time passes and in the comments below @case-silva asked if there was a practical example and @dima-gershman suggested to just use the AVSpeectSynthesizer directly in the ViewController.

    To accommodate both, I've made a simple ViewController example here with a UITextField and a UIButton.

    The flow is:

    1. You enter some text in the textfield (if not, a default value will be set)
    2. You press the button
    3. The button is disabled and the background color is changed (sorry, it was the best I could come up with :))
    4. Once speech is done, the button is enabled, the textfield is cleared and the background color is changed again.

    Here's how it looks

    A Simple UIViewController Example

    import UIKit
    import AVFoundation
    
    class ViewController: UIViewController {
    
        //MARK: Outlets
        @IBOutlet weak var textField: UITextField!
        @IBOutlet weak var speakButton: UIButton!
    
        let synth = AVSpeechSynthesizer()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            synth.delegate = self
        }
    
        @IBAction func speakButtonTapped(_ sender: UIButton) {
            //We're ready to start speaking, disable UI while we're speaking
            view.backgroundColor = .darkGray
            speakButton.isEnabled = false
            let inputText = textField.text ?? ""
            let textToSpeak = inputText.isEmpty ? "Please enter some text" : inputText
    
            let speakUtterance = AVSpeechUtterance(string: textToSpeak)
            synth.speak(speakUtterance)
        }
    }
    
    extension ViewController: AVSpeechSynthesizerDelegate {
        func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
            //Speaking is done, enable speech UI for next round
            speakButton.isEnabled = true
            view.backgroundColor = .lightGray
            textField.text = ""
        }
    }
    

    Hope that gives you a clue Case.