Xcode 10.2.1 / Swift 5.0.1

This chapter was the most challenging so far. This is the code from MainWindowController.swift I ended up with at the end of the chapter 7:

import Cocoa

class MainWindowController: NSWindowController, NSSpeechSynthesizerDelegate,
NSWindowDelegate, NSTableViewDataSource, NSTableViewDelegate {

@IBOutlet weak var textField: NSTextField!
@IBOutlet weak var speakButton: NSButton!
@IBOutlet weak var stopButton: NSButton!
@IBOutlet weak var tableView: NSTableView!

let speechSynth = NSSpeechSynthesizer()

let voices = NSSpeechSynthesizer.availableVoices

var isStarted: Bool = false {
    didSet {
        updateButtons()
    }
}

override var windowNibName: String {
    return "MainWindowController"
}

override func windowDidLoad() {
    super.windowDidLoad()
    updateButtons()
    speechSynth.delegate = self

    for voice in voices {
        print(voiceNameForIdentifier(identifier: voice)!)
    }

    let defaultVoice = NSSpeechSynthesizer.defaultVoice
    if let defaultRow = voices.firstIndex(of: defaultVoice){
        let indices = NSIndexSet(index: defaultRow)
        tableView.selectRowIndexes(indices as IndexSet, byExtendingSelection: false)
        tableView.scrollRowToVisible(defaultRow)
    }
    
}

// MARK: - Action methods

@IBAction func speakIt(sender: NSButton) {
    
    let string = textField.stringValue
    if string.isEmpty {
        print("string is empty")
    } else {
        speechSynth.startSpeaking(string)
        isStarted = true
    }
}

@IBAction func stopIt(sender: NSButton){
    //print("stop button clicked")
    speechSynth.stopSpeaking()
    //isStarted = false
}

func updateButtons() {
    if isStarted {
        speakButton.isEnabled = false
        stopButton.isEnabled = true
    } else {
        stopButton.isEnabled = false
        speakButton.isEnabled = true
    }
}

func voiceNameForIdentifier(identifier: NSSpeechSynthesizer.VoiceName) -> String? {
    let attributes = NSSpeechSynthesizer.attributes(forVoice: identifier)
    return attributes[NSSpeechSynthesizer.VoiceAttributeKey.name] as? String
}

// MARK: - NSSpeechSynthesizerDelegate

func speechSynthesizer(_ sender: NSSpeechSynthesizer,
                       didFinishSpeaking finishedSpeaking: Bool){
    isStarted = false
    print("finishedSpeaking = \(finishedSpeaking)")
}

// MARK: - NSWindowDelegate

func windowShouldClose(sender: AnyObject) -> Bool {
    return !isStarted
}

// MARK: - NSTableViewDataSource

func numberOfRows(in tableView: NSTableView) -> Int {
    return voices.count
}

func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
    let voice = voices[row]
    let voiceName = voiceNameForIdentifier( identifier: voice)
    return voiceName as AnyObject?
}

// MARK: - NSTableViewDelegate

func tableViewSelectionDidChange(_ notification: Notification) {
    let row = tableView.selectedRow
    
    if row == -1 {
        speechSynth.setVoice(nil)
        return
    }
    let voice = voices[row]
    speechSynth.setVoice(voice)
}

}