benglish benglish - 6 months ago 66
Swift Question

Swift iOS Text To Speech not working with "delay" in loop

I'm trying to have the iOS text-to-speech synthesizer "say" a list of phrases with a variable delay between the phrases. For example, I may want to it say "Hello", then wait 5 seconds, then "Is anyone there?", then wait 10 seconds, then say "Hello?"...etc.

I've made a simple example below that illustrates what I am trying to do. I know that the speech synthesizer is speaking, additional utterances are added to a queue and spoken in the order they are received.

I've tried many ways to achieve this delay in the loop. Testing delays with a print statement confirms they are working, but they seem to be interfering with the text-speach-functionality which says the first phrase but waits until the for-loop is done before saying the rest. I thought that any of these types of delays would work as I assume the speech synthesizer is event driven.

I'd appreciate some help, or at least an insight it to why it isn't working. Thanks!

Here is the example code: iPhone 6 simulator, Xcode 7.3

import UIKit
import AVFoundation

class ViewController: UIViewController {

let speechSynthesizer = AVSpeechSynthesizer()
var phraseArray: [String] = ["One", "Two", "Three", "Four", "Five", "Six", "Seven"]

override func viewDidLoad() {
super.viewDidLoad()
for phrase in phraseArray{
let speechUtterance = AVSpeechUtterance(string: phrase)
speechSynthesizer.speakUtterance(speechUtterance)

//"delay()" goes here. It needs to be a variable length delay.

}
}
}


Here are some of the delay methods that I have tried:


  1. Setup the class as a delegate for the speech synthesizer and run a while loop until the synthesizer is finished.

  2. Time based delay:
    referenceDate = NSDate() while(NSDate().timeIntervalSinceDate(referenceDate) < 0.5) {}

  3. I've tried "delay" solutions from stack, like this one: Swift delay in loop

    func delay(delay:Double, closure:()->()) {
    dispatch_after(
    dispatch_time(
    DISPATCH_TIME_NOW,
    Int64(delay * Double(NSEC_PER_SEC))
    ),
    dispatch_get_main_queue(), closure)
    }

  4. Sleep()


Answer

How about something like this:

import UIKit
import AVFoundation

class ViewController: UIViewController {

    let speechSynthesizer = AVSpeechSynthesizer()

    override func viewDidLoad() {
        super.viewDidLoad()

        speak([("Hello", 5.0), ("Is there anyone there?", 10.0), ("Hello?", 0.0)])
    }

    func delay(delay:Double, closure:()->()) {
        dispatch_after(
            dispatch_time(
                DISPATCH_TIME_NOW,
                Int64(delay * Double(NSEC_PER_SEC))
            ),
            dispatch_get_main_queue(), closure)
    }

    func speak(phrases: [(phrase: String, wait: Double)]) {
        if let (phrase, wait) = phrases.first {
            let speechUtterance = AVSpeechUtterance(string: phrase)
            speechSynthesizer.speakUtterance(speechUtterance)
            let rest = Array(phrases.dropFirst())
            if !rest.isEmpty {
                delay(wait) {
                    self.speak(rest)
                }
            }
        }
    }    
}

Notes:

  • An array of tuples is passed to speak. A tuple pair contains a phrase to speak and a delay to wait before the next phrase is spoken.
  • speak takes the first item from the array, speaks the phrase and passes the rest of the array (if not empty) to speak again after waiting for the delay.
  • delay was written by @matt and comes from here.