The ToDo app revisited

For the second midterm exam I asked you to write an app to allow the user to create and view a list of tasks. For the final exam we are going create a SwiftUI app that implements a slightly fancier todo list manager.

The app will have a tabbed interface, with a View tab and a Create tab.

The Create tab will display a single view that the user can use to create new tasks for their calendar.

The View tab will contain a navigation view with a couple of views in it. The first view is a summary of events for the next week. Each entry in the list is a combination of a date and the number of ToDos currently recorded for that date.

Clicking on any one of the items in the summary takes the user to a second view where they can see the list of tasks for that date.

The user can swipe left on any item in this list to delete the task from their list of ToDos.

Useful code

Here is the complete code for the app's model class. This class stores all of the ToDo objects and manages reading them from and writing them to a local file.

import Foundation

class ToDo : Codable, Equatable, Identifiable {
    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
    var id = UUID()

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

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

class Summary {
    var date : Date
    var dateName : String
    var count : Int

    init(_ start : Date,offset o : Int) {
        date = Calendar.current.date(byAdding: .day, value: o, to: start)!
        let formatter = DateFormatter()
        formatter.setLocalizedDateFormatFromTemplate("EEE MMM d")
        dateName = formatter.string(from: date)
        count = 0
    }
}

class AppData : ObservableObject {
    var todos : [ToDo]
    @Published var today : [ToDo]
    @Published var week : [Summary]
    @Published var selectedDate : String = ""

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

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

    func buildSummary() {
        let start = Date.now
        let cal = Calendar.current
        var w : [Summary] = []
        for o in 0..<7 {
            let s = Summary(start,offset:o)
            var c = 0
            for td in todos {
                if cal.isDate(td.date, inSameDayAs: s.date) {
                    c += 1
                }
            }
            s.count = c
            w.append(s)
        }
        week = w
    }

    func filter(_ s : Summary) {
        selectedDate = s.dateName
        today = []
        let cal = Calendar.current
        for td in todos {
            if cal.isDate(td.date, inSameDayAs: s.date) {
                today.append(td)
            }
        }
    }

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

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

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

The model class publishes two lists. The array week is an array of Summary objects for the upcoming week. You will display the contents of this array in the first view in the View tab group. In that list you will display the dateName and the count for each Summary object in the week list. The array today displays a list of ToDo objects for a particular date. You will display that list in the second view in the View tab group. To construct the today list for a particular date, call the filter() method and pass it a Summary object for the day you want to display.

When the user clicks the Create button in the view for creating new ToDos you should call the addTask() method in the model to add the new task, and then also call saveChanges() to save your changes out to the local file.

To remove a task from both the today list and the list of all ToDos, call the removeTask() method, and then call saveChanges() to save your changes to the file.

In the view for creating new ToDo objects you will need to display a date picker. Here is the code you should use to set up that date picker:

DatePicker("",selection: $date,displayedComponents: .date)

Here date is a state variable of type Date.