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()
}
}