Silver Challenge:
To complete this challenge, first, you have to initiate the state of the ‘Stop’ button when the windowController
is created:
/// Document.swift
class Document: NSDocument {
...
override func makeWindowControllers() {
...
viewController.contents = contents
viewController.stopButton.isEnabled = false
...
}
}
Then, you must go into the the ViewController
class and update it’s method so that they update the state of the button when either is clicked (turn ‘Stop’ off when ‘Start’ is still enabled, and vice-versa):
/// ViewController.swift
@IBAction func speakButtonClicked(_ sender: NSButton) {
if let contents = textView.string {
speechSynthesizer.startSpeaking(contents)
stopButton.isEnabled = true
startButton.isEnabled = false
} else {
speechSynthesizer.startSpeaking("The document is empty")
}
}
@IBAction func stopButtonClicked(_ sender: NSButton) {
speechSynthesizer.stopSpeaking()
stopButton.isEnabled = false
startButton.isEnabled = true
}
Then, conform the ViewController
class to the NSSpeechSynthesizerDelegate
, and implement one of its (optional) members:
/// ViewController.swift
class ViewController: NSViewController, NSSpeechSynthesizerDelegate {
...
// Need to specify the delegate to handle executing the functions when the conditions are met
weak var delegate: NSSpeechSynthesizerDelegate?
...
override func viewDidLoad() { // Override this function to ensure delegate is set
super.viewDidLoad()
speechSynthesizer.delegate = self
}
...
// Finally, implement the didFinishSpeaking delegate method
func speechSynthesizer(_ sender: NSSpeechSynthesizer, didFinishSpeaking finishedSpeaking: Bool) {
if finishedSpeaking {
stopButton.isEnabled = false
startButton.isEnabled = true
}
}
}
Gold Challenge
For this one, it took a little bit of work to figure out how to update the progress indicator correctly and ensuring that the values were correct (for both increment and max value of the progress bar).
To do this, I had to:
-
Create a new
NSProgressIndicator
(of the ‘determinate’ variety) and update its display in theviewDidLoad()
method (I put my progress bar at the bottom of the text display). -
Then, manually set the rate of the
speechSynthesizer
so that we can determine approximately how fast thespeechSynthesizer
is speaking (average human-speech is 180-220, according to Apple documentation). I set mine to 200.
@IBOutlet weak var speechProgressBar: NSProgressIndicator!
...
override func viewDidLoad() {
super.viewDidLoad()
speechSynthesizer.delegate = self
speechProgressBar.isHidden = true
speechSynthesizer.rate = 200.0
}
- Using the text supplied by the user, determine how many words there are to read aloud, and determine approximately how long it will take to tay. Set that to the max value of the
NSProgressIndicator
.
@IBAction func speakButtonClicked(_ sender: NSButton) {
if let contents = textView.string {
stopButton.isEnabled = true
startButton.isEnabled = false
speechProgressBar.maxValue = Double(determineSpeechDuration(text: contents))
speechProgressBar.isHidden = false
speechSynthesizer.startSpeaking(contents)
} else {
speechSynthesizer.startSpeaking("The document is empty")
}
}
func determineSpeechDuration(text: String) -> Double {
let words = text.characters.split(separator: " ").map(String.init)
let approximateTimeInMinutes = Double(words.count) / Double(speechSynthesizer.rate) * 100
return approximateTimeInMinutes
}
- Now you can determine the increment based on the words and the max value of the progress bar and then implement the
speechSynthesizer(_:,willSpeakWord:,of:)
delegate method that will increment the progress bar whenever it is about to speak a word
func speechSynthesizer(_ sender: NSSpeechSynthesizer, willSpeakWord characterRange: NSRange, of string: String) {
if let contents = textView.string {
let words = contents.characters.split(separator: " ").map(String.init)
let increment = speechProgressBar.maxValue / Double(words.count)
speechProgressBar.increment(by: increment)
}
}
- And finally, set the progress bar value back to
0.0
every time it finishes speaking, or thestop
button is clicked.
@IBAction func stopButtonClicked(_ sender: NSButton) {
...
speechProgressBar.isHidden = true
speechProgressBar.doubleValue = 0.0
}
func speechSynthesizer(_ sender: NSSpeechSynthesizer, didFinishSpeaking finishedSpeaking: Bool) {
if finishedSpeaking {
...
speechProgressBar.isHidden = true
speechProgressBar.doubleValue = 0.0
}
}