General instructions

For this exam you will be constructing a storyboard application with two views. Do not use SwiftUI for this exam.

This is an open-book exam. You may consult any of the examples posted to the course web site in this exam.

You have seventy minutes to complete your work on the exam. When you are done compress your project folder and email the archive to me for grading.

To Do application

In this exam you will be writing an app to help the user keep track of tasks they need to do.

When the app starts up the first view in the app will allow the user to see all of the things on their to do list for today.

When the user has completed one of their events for the day, they can swipe left to remove that event from their to do list.

Clicking the "New To Do" item in the navigation bar takes the user to a second view where they can add new tasks to their calendar.

This second view includes a text field where the user can enter a title for the new task and a date picker where they can choose a date for the task. Clicking the Save button saves the new task. The user can use the second view to set up multiple tasks. Each time they click the Save button the app will create a new task.

Useful code

Below you will find code for a couple of classes. The ToDo class represents the tasks the user will be creating and viewing in the app. The AppData class is the main model class for the app: it stores the full list of tasks in its todos property along with a filtered list that includes only tasks for today's date in its today property.

class ToDo : Codable, Equatable {
    static func == (lhs: ToDo, rhs: ToDo) -> Bool {
        let cal = Calendar.current
        return lhs.todo == rhs.todo && cal.isDate(lhs.date,inSameDayAs:rhs.date)
    }

    var todo : String
    var date : Date

    init() {
        todo = ""
        date = Date.now
    }

    init(text str : String,time tm : Date) {
        todo = str
        date = tm
    }
}

class AppData {
    var todos : [ToDo]
    var today : [ToDo]

    let itemArchiveURL: URL = {
        let documentsDirectories =
            FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        let documentDirectory = documentsDirectories.first!
        return documentDirectory.appendingPathComponent("todos.plist")
    }()

    init() {
        today = []
        do {
            let data = try Data(contentsOf: itemArchiveURL)
            let unarchiver = PropertyListDecoder()
            let list = try unarchiver.decode([ToDo].self, from: data)
            todos =  list
            filter()
        } catch {
            todos = []
        }
    }

    func filter() {
        today = []
        let now = Date.now
        let cal = Calendar.current
        for td in todos {
            if cal.isDate(td.date, inSameDayAs: now) {
                today.append(td)
            }
        }
    }

    func addTask(_ td : ToDo) {
        todos.append(td)
        filter()
    }

    func removeTask(_ rem : ToDo) {
        todos.removeAll(where: { td in td == rem })
        filter()
    }

    func saveChanges() {
        let encoder = PropertyListEncoder()
        let data = try! encoder.encode(todos)
        try! data.write(to: itemArchiveURL)
    }
}

The AppData class includes code to save ToDos to a file and reload the ToDos from the file automatically when the app starts up.

The first view in your app should display only the ToDo objects in the today array. To add a new ToDo you should call the addTask() method and then call the saveChanges() method to make sure that your new task gets saved to the local file. To delete a ToDo from today's list of tasks you should call the removeTask() method and then call saveChanges().

Working with a DatePicker

You will be using a DatePicker component to allow the user to specify a date for each new ToDo they create. The default DatePicker component you will get when you place a DatePicker in your app's storyboard is set up to allow the user to specify both a calendar date and a time within that day. To change this behavior, set the Mode property for the DatePicker from the default "Date and Time" to simply "Date". The date property of the DatePicker will contain the Date the user selected.