Third Challenge: Add a Window Controller


#1

I’ve had to take three weeks off from studying this book for training with my current job and am still trying to get back in the swing of this book. I’ve made some progress since my break, but I’m uncertain what is needed for this Challenge.

I’ve created the MainWindowController as directed, but can’t figure out how to "move the code for instantiating the view controller within it. I’ve deleted the ImageViewContoller.swift and .xib. I can get the flowViewTemplate or the columnViewTemplate to display by changing the code from flowWindowController.showWindow(self) to columnWindowController…, but can’t figure out the tabs, or if that was even the intent. I’ve made no changes to the SpeakLineViewController yet, so I won’t include it here.

AppDelegate.swift:

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

func applicationDidFinishLaunching(_ aNotification: Notification) {
	
	let flowWindowController = MainWindowController()
	flowWindowController.contentViewController?.title = "Flow"
	flowWindowController.image = NSImage(named: NSImage.Name.flowViewTemplate)

	let columnWindowController = MainWindowController()
	columnWindowController.contentViewController?.title = "Column"
	columnWindowController.image = NSImage(named: NSImage.Name.columnViewTemplate)

	let speakLineViewController = SpeakLineViewController()
	speakLineViewController.title = "SpeakLine"

//		let tabViewController = NSTabViewController()
//		tabViewController.addChildViewController(flowViewController.contentViewController!)
//		tabViewController.addChildViewController(columnViewController.contentViewController!)
//		tabViewController.addChildViewController(speakLineViewController)

	flowWindowController.showWindow(self)
	
}

func applicationWillTerminate(_ aNotification: Notification) {
	// Insert code here to tear down your application
}

}

MainWindowController:

import Cocoa

class MainWindowController: NSWindowController {

@objc dynamic var image: NSImage?

override var windowNibName: NSNib.Name? {
	return NSNib.Name(rawValue: "MainWindowController")
}

override func windowDidLoad() {
    super.windowDidLoad()
}

}

Is this all there is to the Challenge? Or am I way off here and missing the point?

Thanks for any suggestions/help


#2

So, I managed to getting it working this way, with ViewController code moved into MainWindowController (includes the SpeakLine ViewController) (Swift 4.2, Xcode 10.1):

//
//  AppDelegate.swift
//  ViewControl
//
//  Created by Frank on 01/04/18
//

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate
{
    var mainWindowController: MainWindowController?
    
    
    func applicationDidFinishLaunching(_ aNotification: Notification)
    {
        // Create a window controller
        let mainWindowController = MainWindowController()
        
        // Put the window of the window controller on screen
        mainWindowController.showWindow(self)
        
        // Set the property to point to the window controller
        self.mainWindowController = mainWindowController
    }

}

//
//  MainWindowController.swift
//  ViewControl
//
//  Created by Frank on 01/04/18
//

import Cocoa

class MainWindowController: NSWindowController
{
    override var windowNibName: NSNib.Name?
    {
        return "MainWindowController"
    }
    
    
    override func windowDidLoad()
    {
        super.windowDidLoad()
        
        let flowViewController = ImageViewController()
        flowViewController.title = "Flow"
        flowViewController.image = NSImage(named: NSImage.flowViewTemplateName)
        
        let columnViewController = ImageViewController()
        columnViewController.title = "Column"
        columnViewController.image = NSImage(named: NSImage.columnViewTemplateName)
        
        let speakLineViewController = SpeakLineViewController()
        speakLineViewController.title = "SpeakLine"
        
        let tabViewController = NSTabViewController()
        tabViewController.addChild(flowViewController)
        tabViewController.addChild(columnViewController)
        tabViewController.addChild(speakLineViewController)
        
        self.contentViewController = tabViewController
        self.window!.makeKeyAndOrderFront(self)
    }
    
}


//
//  ImageViewController.swift
//  ViewControl
//
//  Created by Frank on 01/04/18
//

import Cocoa

class ImageViewController: NSViewController
{
    
    @objc dynamic var image: NSImage?
    
    
    override var nibName: NSNib.Name?
    {
        return "ImageViewController"
    }
    
    override func viewDidLoad()
    {
        super.viewDidLoad()
        // Do view setup here.
    }
    
}


//
//  SpeakLineViewController.swift
//  ViewControl
//
//  Created by Frank on 01/04/18
//

// NB: ...You will need to connect the DataSource and Delegate by control-clickings on the TableView first,
// and then dragging the dataSource/delegate connections to File Owner.


import Cocoa

class SpeakLineViewController: NSViewController, 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    // array of strings representing available voices
    
    var isStarted: Bool = false
    {
        didSet
        {
            updateButtons()         // when "isStarted" changes, updateButtons() will be called
        }
    }
    
    override var nibName: String
    {
        return "SpeakLineViewController"
    }
    
    
    override func viewDidLoad()
    {
        super.viewDidLoad()
        updateButtons()     // call buttons directly here, to set the inital state of buttons
        speechSynth.delegate = self
        
        for voice in voices
        {
            print(voiceNameForIdentifier(identifier: voice.rawValue)!)
        }
        
        let defaultVoice = NSSpeechSynthesizer.defaultVoice
        
        if let defaultRow = voices.index(of: defaultVoice)
        {
            let indices = IndexSet(integer: defaultRow)
            tableView.selectRowIndexes(indices, byExtendingSelection: false)
            tableView.scrollRowToVisible(defaultRow)
        }
    }
    
    
    
    // MARK: - Action methods
    
    @IBAction func speakIt(sender: NSButton)
    {
        // Get typed-in text as a string
        let string = textField.stringValue
        if string.isEmpty
        {
            print("string is empty")
        }
        else
        {
            print("string is \"\(textField.stringValue)\"")
            speechSynth.startSpeaking(string)
            isStarted = true
        }
    }
    
    
    
    @IBAction func stopIt(sender: NSButton)
    {
        print("stop button clicked")
        speechSynth.stopSpeaking()
    }
    
    
    
    func updateButtons()
    {
        if isStarted
        {
            speakButton.isEnabled = false
            stopButton.isEnabled = true
        }
        else
        {
            stopButton.isEnabled = false
            speakButton.isEnabled = true
        }
    }
    
    
    
    func voiceNameForIdentifier(identifier: String) -> String?
    {
        let attributes: [NSSpeechSynthesizer.VoiceAttributeKey : Any] = NSSpeechSynthesizer.attributes(forVoice: NSSpeechSynthesizer.VoiceName(rawValue: 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: NSWindow) -> Bool
    {
        return !isStarted       // prevents window from closing when speech is started
    }
    
    
    
    // MARK: - NSTableViewDataSource Methods
    
    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.rawValue)
        return voiceName
    }
    
    
    
    // MARK: - NSTableViewDelegate Methods
    
    func tableViewSelectionDidChange(_ notification: Notification)
    {
        let row = tableView.selectedRow
        
        // Set the voice back to the default if the user has deselected all rows
        if row == -1
        {
            speechSynth.setVoice(nil)
            return
        }
        
        let voice = voices[row]
        speechSynth.setVoice(voice)
    }
    
    
}