Confused Confused - 9 days ago 6
iOS Question

How to know when an AVSpeechUtterance has finished, so as to continue app activity?

When an

AVSpeechUtterance
is speaking, I want to wait until it's finished before doing something else.

There is a property of
AVSpeechSynthesizer
that's seemingly indicative of when speech is occuring:

isSpeaking


As stupid and simple as this question might sound, I'm wondering how do I use/check this property to wait until speech has concluded before going on?

ALTERNATIVELY:

There's a delegate, that I'm also clueless as to how to use, which has an ability to do something when an utterance has finished:

AVSpeechSynthesizerDelegate


There's an answer, here, that says to use this. But that doesn't help me because I don't know how to use a Delegate.

UPDATE:



This is how I setup my speaking class:

import AVFoundation

class CanSpeak: NSObject, AVSpeechSynthesizerDelegate {

let voices = AVSpeechSynthesisVoice.speechVoices()
let voiceSynth = AVSpeechSynthesizer()
var voiceToUse: AVSpeechSynthesisVoice?

override init(){
voiceToUse = AVSpeechSynthesisVoice.speechVoices().filter({ $0.name == "Karen" }).first
}

func sayThis(_ phrase: String){
let utterance = AVSpeechUtterance(string: phrase)
utterance.voice = voiceToUse
utterance.rate = 0.5
voiceSynth.speak(utterance)
}
}


UPDATE 2: Wrongheaded workaround...



using the above mentioned
isSpeaking
property, in the gameScene:

voice.sayThis(targetsToSay)

let initialPause = SKAction.wait(forDuration: 1.0)
let holdWhileSpeaking = SKAction.run {
while self.voice.voiceSynth.isSpeaking {print("STILL SPEAKING!")}
}
let pauseAfterSpeaking = SKAction.wait(forDuration: 0.5)
let doneSpeaking = SKAction.run {print("TIME TO GET ON WITH IT!!!")}

run(SKAction.sequence(
[ initialPause,
holdWhileSpeaking,
pauseAfterSpeaking,
doneSpeaking
]))

Answer

Delegate pattern is one of the most common used design pattern in object-oriented programming, it's not as hard as it seems. For your case, you can simply let your class (a game scene) to be a delegate of the CanSpeak class.

protocol CanSpeakDelegate {
   func speechDidFinish()
}

Next set AVSpeechSynthesizerDelegate to your CanSpeak class, declare CanSpeakDelegate and then use AVSpeechSynthesizerDelegate delegate function.

class CanSpeak: NSObject, AVSpeechSynthesizerDelegate {

   let voices = AVSpeechSynthesisVoice.speechVoices()
   let voiceSynth = AVSpeechSynthesizer()
   var voiceToUse: AVSpeechSynthesisVoice?

   var delegate: CanSpeakDelegate!

   override init(){
      voiceToUse = AVSpeechSynthesisVoice.speechVoices().filter({ $0.name == "Karen" }).first
      self.voiceSynth.delegate = self
   }

   func sayThis(_ phrase: String){
      let utterance = AVSpeechUtterance(string: phrase)
      utterance.voice = voiceToUse
      utterance.rate = 0.5
      voiceSynth.speak(utterance)
   }

   func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
      self.delegate.speechDidFinish()
   }
}

Lastly in your game scene class, simply conform to CanSpeakDelegate and set it as the delegate of your CanSpeak class.

class GameScene: NSObject, CanSpeakDelegate {

   let canSpeak = CanSpeak()

   override init() {
      self.canSpeak.delegate = self
   }

   // This function will be called every time a speech finishes
   func speechDidFinish() {
      // Do something
   }
}