swiftxcodeavfoundationavspeechsynthesizeravspeechutterance

AVSpeechSynthesiser bugging ViewDidLoad function


Whenever I navigate to this view controller, the speakText() function in viewDidLoad for some reason doesn't work and the view controller takes a long time to load (this is not the case when I remove that line). Above that line, a text label's text is set to the string that is meant to be spoken, and works fine. Each time recordTapped is called, the speech synthesis works perfectly. However, it just doesn't seem to work in viewDidLoad.

UPDATE I have done some more testing, after assigning the view controller delegate of the speech synthesiser and running the function asynchronously. However, it doesn't work. I set the view controller to initial view controller and it still doesn't work, so it isn't a problem in navigation and load time is still very long.

import UIKit
import AVFoundation

class StartViewController: UIViewController, AVAudioRecorderDelegate {

    @IBOutlet weak var questionLabel: UILabel!
    @IBOutlet weak var recordButton: UIButton!
    let synth = AVSpeechSynthesizer()
    var myUtterance = AVSpeechUtterance(string: "")
    var number = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        synth.delegate? = self as! AVSpeechSynthesizerDelegate
        number = 0
        DispatchQueue.global(qos: .userInitiated).async       {self.speakText(int: number)}
        self.number = self.number + 1
    }

    @IBAction func recordTapped(_ sender: UIButton) {
        speakText(int: number)
        number = number + 1
    }

    func speakText(int: Int) {

    myUtterance = AVSpeechUtterance(string: "the number is \(int)")
    myUtterance.voice = AVSpeechSynthesisVoice(language: "en-AU")
    synth.speak(myUtterance)

    }

}

Solution

  • As I said in the comments, multithreading works. Just tested myself. Working code below, please test and confirm. It is always good to rely on separate thread for speech.

    import UIKit
    import AVFoundation
    
    class ViewController: UIViewController {
    
        @IBOutlet weak var questionLabel: UILabel!
        @IBOutlet weak var recordButton: UIButton!
    
        var number = 0
    
        @IBAction func recordTapped(_ sender: UIButton) {
            questionLabel.text = "\(number)"
            speakTest(int: number)
            number += 1
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
            number = 0
            questionLabel.text = "\(number)"
            DispatchQueue.global(qos: .userInitiated).async {
                self.speakTest(int: self.number)
            }
    //        speakTest(int: number)
    
            // Do any additional setup after loading the view, typically from a nib.
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
        func speakTest(int: Int) {
            let utterance = AVSpeechUtterance(string: "Number is \(int)")
            let synth = AVSpeechSynthesizer()
            synth.speak(utterance)
        }
    }
    

    Plus, To call Speech whenever view appears, call it from viewWillAppear() or viewDidAppear() and not from viewDidLoad(), since the latter is called once when the app is in foreground.