Swift 4.1, Xcode 9.4 version, up to the Challenges


#1

A lot of differences between the book and Swift 4.1. The following code works and populates the table view with the data for the upcoming classes. This is the code up to the section “Opening URLs” in the book on page 418 (not the eBook version).

If anyone sees mistakes or sees a better way to do this, I’m open to all suggestions/corrections.

Thanks!

AppDelegate.swift:

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 so AppDelegate can keep a reference to it.
	self.mainWindowController = mainWindowController
}

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

MainWindowController.swift:

	class MainWindowController: NSWindowController {

@IBOutlet var tableView: NSTableView!
@IBOutlet var arrayController: NSArrayController!

let fetcher = ScheduleFetcher()
@objc dynamic var courses: [Course] = []

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

override func windowDidLoad() {
	super.windowDidLoad()
	
	tableView.target = self
	tableView.doubleAction = #selector(openClass(sender:))
	
	
	fetcher.fetchCoursesUsing { (result) in
		switch result {
		case .Success(let courses):
			print("Got courses: \(courses)")
			self.courses = courses
		case .Failure(let error):
			print("Got error: \(error)")
			NSAlert(error: error).runModal()
			self.courses = []
		}
	}
}

@objc func openClass(sender: AnyObject) {
	if let course = arrayController.selectedObjects.first as? Course {
		NSWorkspace.shared.open(course.url as URL)
	}
}
}

ScheduleFetcher.swift:

import Foundation

class ScheduleFetcher {

enum FetchCoursesResult {
	case Success([Course])
	case Failure(NSError)
}

let session: URLSession
let urlString = "http://bookapi.bignerdranch.com/courses.json"

init() {
	let config = URLSessionConfiguration.default
	session = URLSession(configuration: config)
}

//------------------------------------------------------------------------

func fetchCoursesUsing(completionHandler: @escaping(FetchCoursesResult) -> (Void)) {
	let url = URL(string: urlString)!
	let request = URLRequest(url: url as URL)
	
	let task = session.dataTask(with: request, completionHandler: {
		(data, response, error) -> Void in
		var result: FetchCoursesResult
		
		if data == nil {
			result = .Failure(error! as NSError)
		}
		else if let response = response as? HTTPURLResponse  {
			print("\(String(describing: data?.count)) bytes, HTTP \(response.statusCode).")
			if response.statusCode == 200 {
				result = self.resultFromData(data: data!)
			}
			else {
				let error = self.errorWithCode(code: 1,
								localizedDescription: "Bad status code \(response.statusCode).")
				result = .Failure(error)
			}
		}
		else {
			let error = self.errorWithCode(code: 1,
										   localizedDescription: "Unexpected response object.")
			result = .Failure(error)
		}
		OperationQueue.main.addOperation({
			completionHandler(result)
		})
		
	})
	task.resume()
}

//------------------------------------------------------------------------

func errorWithCode(code: Int, localizedDescription: String) -> NSError {
	return NSError(domain: "ScheduleFetcher",
				   code: code,
				   userInfo: [NSLocalizedDescriptionKey: localizedDescription])
}

//------------------------------------------------------------------------

func courseFromDictionary(courseDict: NSDictionary) -> Course? {
	if let title = courseDict["title"] as? String,
		let urlString = courseDict["url"] as? String,
		let upcomingArray = courseDict["upcoming"] as? [NSDictionary],
		let nextUpcomingDict = upcomingArray.first,
		let nextStartDateString = nextUpcomingDict["start_date"] as? String
	{
		let url = NSURL(string: urlString)!
		
		let dateFormatter = DateFormatter()			// book says to use var here, causes warning
		dateFormatter.dateFormat = "yyyy-MM-dd"
		let nextStartDate = dateFormatter.date(from: nextStartDateString)!
		
		return Course(title: title,
					  url: url,
					  nextStartDate: nextStartDate as NSDate)
	}
	return nil
}

//------------------------------------------------------------------------

func resultFromData(data: Data) -> FetchCoursesResult {

	var topLevelDict = Dictionary<String, Any>()
	do {
		topLevelDict = (try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any])!
	} catch {
		return .Failure(error as NSError)
	}
	let courseDicts = topLevelDict["courses"] as! [NSDictionary]
	var courses: [Course] = []
	for courseDict in courseDicts {
		if let course = courseFromDictionary(courseDict: courseDict){
			courses.append(course)
		}
	}
	return .Success(courses)
	
	
}

//------------------------------------------------------------------------
}

Course.swift:

import Foundation

class Course: NSObject {
@objc dynamic let title:String
@objc dynamic let url: NSURL
@objc dynamic let nextStartDate: NSDate

init(title: String, url: NSURL, nextStartDate: NSDate) {
	self.title = title
	self.url = url
	self.nextStartDate = nextStartDate
	super.init()
}
}