Skip to content

This README file only pretends to be a presentation for the real project.

License

Notifications You must be signed in to change notification settings

c4arl0s/ChronoList-Presentation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 

Repository files navigation

ChronoList-Presentation

In order to update an existing Git submodule execute: (You might need permissions from Owner to get submodules)

git submodule update --remote --merge
  1. First ideas
  2. Modify the cell to add a Start/Done button
  3. Modify ToDo Model
  4. Replace MyToDoTableViewController with ToDoViewController
  5. Fix Checkmark button image when is selected
  6. Fix Show Add New ToDo Segue
  7. Fix Show Details To Do Segue
  8. Fix Issue when you tap save button to a new ToDo object
  9. Fix selected Cell
  10. Modify Due Date Cell Constraints
  11. Fix Edit button
  12. Fix Save button after select any table view cell to show details
  13. Use PropertyKey struct
  14. Fix Date Picker Cell
  15. Fix dismiss keyboard when you tap Return button keyboard
  16. Create Layout for CalendarView and ToDoTableView
  17. Create CalendarView.xib and layout
  18. Create load calendar view
  19. Add ProjectName property to ToDo class and NewProjectNameViewController
  20. Replace to Plus sign in CalendarView
  21. Modify the number of Tasks
  22. Modify Add button according to design
  23. Add SegmentedControl according to new Design
  24. Add a row in CalendarView to show the day number
  25. Move hour Labels to the right
  26. Create a dayFilterSegmentedControl UISegmentedControl
  27. Create filter function for Today, Yesterday and Tomorrow
  28. When you tap any cell, you have to inject the correct ToDo object, according to suitable array
  29. When you create a new ToDo, save it correctly and show it in table view
  30. Change Project's name
  31. Write ToDoController, basic functionality.
  32. Fix the AddButton.swift file again
  33. git reset --hard ba0e0b2
  34. Rename project to ChronoList again
  35. Rename Targets to ChronoList, ChronoListTests, and ChronoListUITests
  36. Write ToDoController in a new brach, basic functionality, again
  37. Use ToDoController to save and load ToDo objects
  38. Create function to filter yesterday ToDos in ToDoController
  39. When you tap Yesterday Segmented COntrol button, update table to show yesterday ToDo
  40. Fix when you add and edit a ToDo object
  41. Improve unwindToToDoList(sender:) after you save a new ToDo object
  42. Move check box button to the right
  43. Once you tap addButton show keyboard right away
  44. Move Segmented Control outside of toolbar
  45. In New To Do Viewcontroller, move check box to the right.

ChronoList

Simple To Do list app with time tracker

IMG_1399

  1. Clear constraints of the text field.
  2. Resize the text field to add Start/done button.
  3. Set constraints to Start/Done button.
  • Alignment: vertically in container.
  • Width: greater or equal to 46.

Screen Shot 2020-10-10 at 13 18 43

  1. Set constraints to text field:
  • Alignment: vertically in container.
  • Horizontal space constraint: Constant 8 (from the textField to checkMarkButton, and from the text field to the StartButton.

Screen Shot 2020-10-10 at 13 19 33

  1. Build and run.

Screen Shot 2020-10-10 at 13 32 51

Current:

var title: String
var isComplete: Bool
var dueDate: Date
var note: String?

Which are the requirements ?

  1. you have to store the due date, which you already have
  2. I want to store the hour I stared the ToDo. We can name it: startDate
  3. I want to store the hour I finish the ToDo. We can name it: finishDate.
  4. If startDate and finishDate are not in the same dueDate, you can't do the ToDo activity. You will not able to do it, You have to re schedule. This activity have to be registered as a non acomplished activity. It would be bad for you if this happens. What you plan, you have to do it.

I think if we add this properties I can calculate the performance of a week

var title: String
var isComplete: Bool
var dueDate: Date
var startDate: Date
var finishDate: Date
var note: String?

Screen Shot 2020-10-11 at 13 04 21

As when you create a new ToDo Activity you don't need to fill the startDate and finishDate, you can declare both properties as optionals. If you declared as stored properties, it throws a compile error. So,

var title: String
var isComplete: Bool
var dueDate: Date
var startDate: Date?
var finishDate: Date?
var note: String?

Build and run, it is ok!

  1. Delete MyTodoTableViewController.
  2. Conect ToDoViewController with ToDoVieController swift file. (all Outlets and subclass)
  3. Drag and drop a new TableViewCell into the TableView.
  4. Connect cell with custom cell: ToDoTableViewCell
  5. Register the xib file for the cell and create the cell in appropiate Table View Data source method. Create the segues to trigger the NewToDoTableViewController.

Remember to configure Button State: Default

Screen Shot 2020-10-15 at 13 56 49

Configure button state: Selected

Screen Shot 2020-10-15 at 13 55 51

  1. I have to delete the navigation controller.
  2. Embed NewToDoViewController into new navigation controller.
  3. Drag and drop from + button to navigation controller.
  4. Set identifier to: ShowAddNewToDo.

Screen Shot 2020-10-18 at 1 42 53

  1. Finally, keep the same piece of code in prepare(forSegue:sender:) method:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "showDetails" {
        let toDoViewController = segue.destination as! NewToDoViewController
        let indexPath = tableView.indexPathForSelectedRow!
        let selectedTodo = toDos[indexPath.row]
        toDoViewController.toDo = selectedTodo
    }
}

Build and run.

Screen Recording 2020-10-18 at 1 46 27 2020-10-18 01_48_57

  1. Drag and drop from the table view cell to the NewToDoViewController directly. (no passing through navigation controller) Like in the image, select Show.

Screen Shot 2020-10-18 at 2 57 20

  1. When you did select the cell, perform the segue:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    performSegue(withIdentifier: "ShowDetailsToDo", sender: self)
}
  1. Uncheck Enabled Control mark in Title text Field, inside ToDoTableViewCell.xib:

Screen Shot 2020-10-18 at 2 43 51

So, now when you tap, the segue is trigger easily.

The segue identifier was missing. So you have to set again: SaveUnwind

Screen Shot 2020-10-19 at 9 40 01

Then you can save the ToDo object again.

When you select a cell you show the AddNewTodoViewController, when it dismissed, the cell has to be deselected.

Screen Recording 2020-10-20 at 11 36 16 2020-10-20 11_37_17

The solution is to call the instance method deselectedRow(at:,animated:) of the tableView:

tableView.deselectRow(at: selectedIndexPath, animated: true)

You have to call it in the prepare(for segue:,sender:) method:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "ShowDetailsToDo" {
        let toDoViewController = segue.destination as! NewToDoViewController
        let selectedIndexPath = tableView.indexPathForSelectedRow!
        tableView.deselectRow(at: selectedIndexPath, animated: true)
        let selectedToDo = toDos[selectedIndexPath.row]
        toDoViewController.toDo = selectedToDo
    }
}

I tried to do it in didSelecteIndext(at indexPath:) but this method clears the indexPathForSelectedRow property, and this property is required in prepare(for segue:,sender) method, so, the application crashes when it tries to access the property.

Build and run. Now the cell is deselected after you tap any cell.

Screen Recording 2020-10-20 at 11 50 09 2020-10-20 11_51_35

The actual Due Date Cell has a stack view in its container. Suppress this horizontal stack view, because I think it is too complex to use when the cell only has to subviews (2 labels).

Before:

Screen Shot 2020-10-22 at 15 23 13

After:

Screen Shot 2020-10-22 at 15 22 04

  • **Given It displays to do table view controller

  • **When I tap Edit button

  • **Then Display Delete button correctly, and set editing mode

  • **Given It displays to do table view controller.

  • **When I tap Done button

  • **Then Display Edit button correctly, and unset editing mode.

  1. Drag and drop UIBarButtonItem object, name it editBarItemButton set System Item to Custom.
  2. Create an IBOutlet for editBarItemButton
@IBOutlet weak var editBarButtonItem: UIBarButtonItem!
  1. Set bar button item title to "Edit" in viewDidLoad() because as it is a custom item, it needs suitable title
editBarButtonItem.title = "Edit"
  1. Create an IBAction for editBarButtonItemTapped and do the logic:
@IBAction func editBarButtonItemTapped(_ sender: UIBarButtonItem) {
    if !tableView.isEditing {
        tableView.setEditing(true, animated: true)
        sender.title = "Done"
    } else {
        tableView.setEditing(false, animated: true)
        sender.title = "Edit"
    }
}
  1. Do not forget connect the action.
  2. Build and run.

Screen Recording 2020-10-26 at 22 17 27 2020-10-26 22_19_21

  • **Given I am displaying Details of a note.
  • **When I tap Save button
  • **Then save actual toDo object and do not duplicate.
  1. Reviewing the piece of code that saves the toDo instance, I did find that I was clearing the property indexPathForSelectedRow when I was preparing the segue, so the flow of the program was not updating the toDo instance. This did duplicate the toDo instance. To fix this, first remove the line where you were clearing the property, this was located in prepare(forSegue:) method.
  2. Modify unwindToToDoList(segue:) method, replace the logic with a switch case statement.
@IBAction func unwindToToDoList(segue: UIStoryboardSegue) {
    switch segue.identifier {
    case "SaveUnwind":
        let sourceViewController = segue.source as! NewToDoViewController
        if let toDo = sourceViewController.toDo {
            if let selectedIndexPath = tableView.indexPathForSelectedRow {
                toDos[selectedIndexPath.row] = toDo
                tableView.reloadRows(at: [selectedIndexPath], with: .none)
                tableView.deselectRow(at: selectedIndexPath, animated: true)
            } else {
                let newIndexPath = IndexPath(row: toDos.count, section: 0)
                toDos.append(toDo)
                tableView.insertRows(at: [newIndexPath], with: .automatic)
            }
            ToDo.saveToDos(toDos)
        }
    case "CancelUnwind":
        if let selectedIndexPath = tableView.indexPathForSelectedRow {
        tableView.deselectRow(at: selectedIndexPath, animated: true)
        }
    default:
        preconditionFailure("No segue identified")
    }
}
  1. Verify the CancelUnwind identifier correspond to the segue from Cancel Button to Exit object.

Screen Shot 2020-10-27 at 1 21 41

  1. Build and run.

Screen Recording 2020-10-27 at 1 30 03 2020-10-27 01_32_55

  1. Use it in NewToDoTableViewController
struct PropertyKeys {
    static let SaveUnwind = "SaveUnwind"
}
  1. Use it in ToDoTableViewController
struct PropertyKeys {
    static let ToDoCellIdentifier = "ToDoCellIdentifier"
    static let ShowDetailsToDo = "ShowDetailsToDo"
    static let SaveUnwind = "SaveUnwind"
    static let CancelUnwind = "CancelUnwind"
}

Screen Shot 2020-10-27 at 13 28 05

Screen Shot 2020-10-27 at 13 29 15

Build and run again.

Screen Shot 2020-10-27 at 13 30 50

  1. Make Conform ToDoViewController to UITextFieldDelegate protocol
class NewToDoViewController: UITableViewController, UITextFieldDelegate {
  1. Implement textFieldShouldReturn(textField:) delegate method, and make textField to resign first responder:
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    textField.resignFirstResponder()
}
  1. Build and run. Test.

Screen Recording 2020-10-29 at 22 36 57 2020-10-29 22_42_37

  1. Clear tableView constraints.
  2. Embed tableView into vertical stack view.
  3. Drag and drop an UIView object.
  4. Set Distribution to Fill Equally,
  5. Set vertical stack view constraints to trail = 0, leading = 0, top = 0 and bottom to 0.
  6. In toolbar object, delete the space, the add button will be placed at the left corner.
  7. Build and run.

Screen Shot 2020-10-30 at 0 55 07

Fist attempt

Screen Shot 2020-10-30 at 2 14 59

  1. Make CalendarView File's owner ToDoViewController

Screen Shot 2020-10-30 at 18 06 12

  1. Create an IBOutlet in ToDoViewController.swift
@IBOutlet weak var calendarView: UIView!
  1. Make sure to connect the outlet inside CalendarView.xib as in the figure:

Screen Shot 2020-10-30 at 18 18 34

  1. Load calendar view in viewDidLoad() method:
loadCalendarView()

Then:

Bundle.main.loadNibNamed("CalendarView", owner: self, options: nil)
calendarView.frame.origin = CGPoint(x: 0, y: 55)
view.addSubview(calendarView)

Build and run.

Screen Shot 2020-10-30 at 19 05 09

import Foundation

struct ToDo: Codable {
    var projectName: String
    var title: String
    var isComplete: Bool
    var dueDate: Date
    var startDate: Date?
    var finishDate: Date?
    var note: String?
    static let DocumentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    static let ArchiveURL = DocumentsDirectory.appendingPathComponent("todos").appendingPathExtension("plist")
    
    init(projectName: String, title: String, isComplete: Bool, dueDate: Date, note: String?)
    {
        self.projectName = projectName
        self.title = title
        self.isComplete = isComplete
        self.dueDate = dueDate
        self.note = note
    }
    
    static func loadToDos() -> [ToDo]? {
        guard let codedToDos = try? Data(contentsOf: ArchiveURL) else {return nil}
        let propertyListDecoder = PropertyListDecoder()
        return try? propertyListDecoder.decode(Array<ToDo>.self, from: codedToDos)
    }
    
    static func saveToDos(_ toDos:[ToDo]) {
        let propertyListEncoder = PropertyListEncoder()
        let codedToDos = try? propertyListEncoder.encode(toDos)
        try? codedToDos?.write(to: ArchiveURL, options: .noFileProtection)
    }
    
    static func loadSampleToDos() -> [ToDo] {
        let todo1 = ToDo(projectName: "p1", title: "ToDo 1", isComplete: false, dueDate: Date(), note: "Sample Note 1")
        let todo2 = ToDo(projectName: "p1", title: "ToDo 2", isComplete: false, dueDate: Date(), note: "Sample Note 2")
        let todo3 = ToDo(projectName: "p1", title: "ToDo 3", isComplete: false, dueDate: Date(), note: "Sample Note 3")
        let todo4 = ToDo(projectName: "p1", title: "ToDo 4", isComplete: false, dueDate: Date(), note: "Sample Note 4")
        let todo5 = ToDo(projectName: "p1", title: "ToDo 5", isComplete: false, dueDate: Date(), note: "Sample Note 5")
        return [todo1, todo2, todo3, todo4, todo5]
    }
    
    static let dueDateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .short
        formatter.timeStyle = .short
        return formatter
    }()
}

Modify temporary prepareForSegue method in NewToDoTableViewController:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    super.prepare(for: segue, sender: sender)
    guard segue.identifier == PropertyKeys.SaveUnwind else { return }
    let title = titleTextField.text!
    let isComplete = isCompletButton.isSelected
    let dueDate = dueDatePickerView.date
    let note = notesTextView.text
    toDo = ToDo(projectName: "p1", title: title, isComplete: isComplete, dueDate: dueDate, note: note)
}

Create AddProjectNameViewController:

import UIKit

class AddProjectNameViewController: UIViewController, UITextFieldDelegate {
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var textField: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        registerForKeyNotification()
    }
    
    func registerForKeyNotification() {
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(_:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    }
    
    @objc func keyboardWasShown(_ notification: NSNotification) {
        guard let info = notification.userInfo, let keyboardFrameValue = info[UIKeyboardFrameBeginUserInfoKey] as? NSValue else { return }
        let keyboardFrame = keyboardFrameValue.cgRectValue
        let tableViewWithKeyboardContentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardFrame.size.height, right: 0.0)
        tableView.contentInset = tableViewWithKeyboardContentInsets
    }
    
    @objc func keyboardWillBeHidden(_ notification: NSNotification) {
        let contentInsets = UIEdgeInsets.zero
        tableView.contentInset = contentInsets
        tableView.scrollIndicatorInsets = contentInsets
    }
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
    }
}

Build and run.

Screen Recording 2020-11-01 at 1 45 23 2020-11-01 01_46_52

At this stage, Xcode 12.2 was released so I had to deployed the target to iOS 14.2

Use AddButton.swift file to customize each button.

Screen Shot 2020-12-12 at 18 33 05

Screen Shot 2020-12-12 at 18 34 09

import UIKit
@IBDesignable       

class AddButton: UIButton {
    @IBInspectable var fillColor: UIColor = UIColor.orange
    private var halfWidth: CGFloat {
        return bounds.width / 2
    }
    private var halfHeight: CGFloat {
        return bounds.height / 2
    }
    
    override func draw(_ rect: CGRect) {
        let bezierPath = UIBezierPath(ovalIn: rect)
        fillColor.setFill()
        bezierPath.fill()
        let plusWidth: CGFloat = min(bounds.width, bounds.height)*0.6
        let halfPlusWidth = plusWidth / 2
        let plusPath = UIBezierPath()
        plusPath.lineWidth = 4.0
        plusPath.move(to: CGPoint(x: halfWidth - halfPlusWidth, y: halfHeight))
        plusPath.addLine(to: CGPoint(x: halfWidth + halfPlusWidth, y: halfHeight))
        plusPath.move(to: CGPoint(x: halfWidth, y: halfHeight-halfPlusWidth))
        plusPath.addLine(to: CGPoint(x: halfWidth, y: halfHeight+halfPlusWidth))
        UIColor.white.setStroke()
        plusPath.stroke()
    }
    
    func pulsate() {
        let pulse = CASpringAnimation(keyPath: "transform.scale")
        pulse.duration = 0.5
        pulse.fromValue = 0.98
        pulse.toValue = 1.1
        pulse.initialVelocity = 1.0
        pulse.damping = 1.0
        layer.add(pulse, forKey: nil)
    }
}

Build and run.

Screen Shot 2020-12-12 at 18 31 44

As you know, day has 8 official hours to work, modify CalendarView to show 8 hours plus 2 hours to eat.

  1. As you know, you used Stack views to create CalendarView, You just copy/paste a row to increase the number of tasks and change the labels corresponding to the hour.

Screen Shot 2020-12-16 at 16 53 15

  1. Build and run.

Screen Shot 2020-12-16 at 16 55 02

At this stage, The render and update auto layout failed.

The new design paints an outside ring, in the center paints a plus sign.

  1. First paint the outside ring:
let center = CGPoint(x: bounds.width/2, y: bounds.height/2)
let radius: CGFloat = min(bounds.width/2, bounds.height/2)
let startAngle: CGFloat = 0.0
let endAngle: CGFloat = .pi*2
let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
path.lineWidth = 2.0
plusColor.setStroke()
path.stroke()
  1. Second, it paints the plus sign
let plusWidth: CGFloat = min(bounds.width, bounds.height)*0.6
let halfPlusWidth = plusWidth/2
let plusPath = UIBezierPath()
plusPath.lineWidth = 3.0
plusPath.move(to: CGPoint(x: halfWidth - halfPlusWidth, y: halfHeight))
plusPath.addLine(to: CGPoint(x: halfWidth + halfPlusWidth, y: halfHeight))
plusPath.move(to: CGPoint(x: halfWidth, y: halfHeight-halfPlusWidth))
plusPath.addLine(to: CGPoint(x: halfWidth, y: halfHeight+halfPlusWidth))
UIColor.black.setStroke()
plusColor.setStroke()
plusPath.stroke()
  1. Build and run:

Screen Shot 2020-12-17 at 18 19 41

  1. This time it paints correctly the size of the button, It was solve changing the size of the radious:
let radius: CGFloat = min(bounds.width/2, bounds.height/2)

According to new design, insert an segmented control to the toolbar, then you are going to show Yesterday, tomorrow and today controls. Remove the add button on toolbar.

We are going to use segmented control to filter toDo list by date (yesterday, today and tomorrow). We also add a Segmented control to Last, Current and Next Week:

Screen Shot 2020-12-19 at 23 13 34

Screen Shot 2020-12-19 at 23 39 01

You just move the hour label to the lowest place in the stack view.

Screen Shot 2020-12-21 at 6 28 47

  1. In ToDoViewController declare an UISegmentedControl object and connect it to canvas. This would filter an specific day of the week, in this case we will filter today, yesterday and tomorrow.
@IBOutlet weak var dayFilterSegmentedControl: UISegmentedControl!

It is not needed at this stage, but it is done !

  1. In ToDoViewController create an IBAction for the segmented control UISegmentedControl and connect it to canvas, act.
@IBAction func filterOptionUpdated(_ sender: UISegmentedControl) {

}
  1. Create a method to create a new array of toDos that correspond the day and connect it to canvas:
@IBAction func filterOptionUpdated(_ sender: UISegmentedControl) {
    updateMatchingToDos()
}
  1. Write updateMatchingToDos method, first, we need empty the toDos array and reload data each time the segmented control change.
func updateMatchingToDos() {
    toDos = []
    self.tableView.reloadData()
}

Screen Recording 2020-12-22 at 12 46 59 2020-12-22 12_47_50

  1. Create array of day options:
let dayOptions = ["Yesterday", "Today", "Tomorrow"];
  1. Create day option variable, initialize to "Yesterday"
var dayOption: String = "Yesterday"
  1. Create an global instance of Calendar:
let calendar: Calendar = {
    var calendar = Calendar.current
    calendar.timeZone = TimeZone.current
    return calendar
}()
  1. Create a function loadToDos(), in this function load toDos in order if there are saved toDos or if there is no saved todos, load them from samples. After that, you have to checked the value of dayOption according to selected Segmented index and then filter each toDo with its corresponding array.
func loadToDos() {
    if let unwrappedToDos = ToDo.loadToDos() {
        toDos = unwrappedToDos
    } else {
        toDos = ToDo.loadSampleToDos()
    }
    dayOption = dayOptions[dayFilterSegmentedControl.selectedSegmentIndex]
    switch dayOption {
    case "Today":
        todayToDos = filterToDosFromToday(toDos: toDos)
    case "Yesterday":
        yesterdayToDos = filterToDosFromYesterday(toDos: toDos)
    case "Tomorrow":
        tomorrowToDos = filterToDosFromTomorrow(toDos: toDos)
    case "All":
        allToDos = toDos
    default:
        allToDos = toDos
    }
}
  1. Each time you tap the segmentd control you have to update the number of rows in section and the cell for row at indexPath:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    dayOption = dayOptions[dayFilterSegmentedControl.selectedSegmentIndex];
    switch dayOption {
    case "Today":
        return todayToDos.count
    case "Yesterday":
        return yesterdayToDos.count
    case "Tomorrow":
        return tomorrowToDos.count
    default:
        return toDos.count
    }
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: PropertyKeys.ToDoCellIdentifier) as? ToDoTableViewCell else { fatalError("Could not dequeue a cell") }
    cell.delegate = self
    dayOption = dayOptions[dayFilterSegmentedControl.selectedSegmentIndex];
    switch dayOption {
    case "Today":
        let toDo = todayToDos[indexPath.row]
        cell.titleTextField?.text = toDo.title
        cell.isCompleteButton.isSelected = toDo.isComplete
    case "Yesterday":
        let toDo = yesterdayToDos[indexPath.row]
        cell.titleTextField?.text = toDo.title
        cell.isCompleteButton.isSelected = toDo.isComplete
    case "Tomorrow":
        let toDo = tomorrowToDos[indexPath.row]
        cell.titleTextField?.text = toDo.title
        cell.isCompleteButton.isSelected = toDo.isComplete
    default:
        let toDo = toDos[indexPath.row]
        cell.titleTextField?.text = toDo.title
        cell.isCompleteButton.isSelected = toDo.isComplete
    }
    return cell
}
  1. Update updateMatchingToDos method:
func updateMatchingToDos() {
    dayOption = dayOptions[dayFilterSegmentedControl.selectedSegmentIndex];
    switch dayOption {
    case "Today":
        todayToDos = self.filterToDosFromToday(toDos: toDos)
    case "Yesterday":
        yesterdayToDos = self.filterToDosFromYesterday(toDos: toDos)
    case "Tomorrow":
        tomorrowToDos = self.filterToDosFromTomorrow(toDos: toDos)
    case "All":
        allToDos = toDos
    default:
        toDos = []
    }
    self.tableView.reloadData()
}
  1. Create each method to filter suitable array of ToDos
func filterToDosFromToday(toDos: [ToDo]) -> [ToDo] {
    todayToDos = []
    for toDo in toDos {
        if calendar.isDateInToday(toDo.dueDate) {
            todayToDos.append(toDo)
        }
    }
    return todayToDos
}

func filterToDosFromYesterday(toDos: [ToDo]) -> [ToDo] {
    yesterdayToDos = []
    for toDo in toDos {
        if calendar.isDateInYesterday(toDo.dueDate) {
            yesterdayToDos.append(toDo)
        }
    }
    return yesterdayToDos
}

func filterToDosFromTomorrow(toDos: [ToDo]) -> [ToDo] {
    tomorrowToDos = []
    for toDo in toDos {
        if calendar.isDateInTomorrow(toDo.dueDate) {
            tomorrowToDos.append(toDo)
        }
    }
    return tomorrowToDos
}
  1. Remember to declare global array variables to hold suitable values, Use ToDo.swift file
var toDos = [ToDo]()
var todayToDos = [ToDo]()
var yesterdayToDos = [ToDo]()
var tomorrowToDos = [ToDo]()
var allToDos = [ToDo]()
  1. Build and run. Each time you tap the segmented control, you can see filtered each group of arrays:

Screen Recording 2020-12-23 at 12 15 39 2020-12-23 12_16_47

There is a problem when you tap any cell, the object that is injected in NewTodoViewController is not suitable. To fix this, you have to pass the correct object instance. In prepare(segue:,sender:) method, inject the correct object instance:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == PropertyKeys.ShowDetailsToDo {
        let toDoViewController = segue.destination as! NewToDoViewController
        let selectedIndexPath = tableView.indexPathForSelectedRow!
        dayOption = dayOptions[dayFilterSegmentedControl.selectedSegmentIndex];
        var selectedToDo: ToDo
        switch dayOption {
        case "Today":
            selectedToDo = todayToDos[selectedIndexPath.row]
        case "Yesterday":
            selectedToDo = yesterdayToDos[selectedIndexPath.row]
        case "Tomorrow":
            selectedToDo = tomorrowToDos[selectedIndexPath.row]
        case "All":
            selectedToDo = toDos[selectedIndexPath.row]
        default:
            preconditionFailure("No day option value")
        }
        toDoViewController.toDo = selectedToDo
    }
}

Screen Recording 2020-12-23 at 22 44 29 2020-12-23 22_46_07

The issue was solved modifying unwindToToDoList(segue:), just right after you check that there is no selectedIndexPath:

@IBAction func unwindToToDoList(segue: UIStoryboardSegue) {
    switch segue.identifier {
    case PropertyKeys.SaveUnwind:
        let sourceViewController = segue.source as! NewToDoViewController
        if let toDo = sourceViewController.toDo {
            if let selectedIndexPath = tableView.indexPathForSelectedRow {
                toDos[selectedIndexPath.row] = toDo
                tableView.reloadRows(at: [selectedIndexPath], with: .none)
                tableView.deselectRow(at: selectedIndexPath, animated: true)
            } else {
                var newIndexPath: IndexPath
                dayOption = dayOptions[dayFilterSegmentedControl.selectedSegmentIndex]
                if dayOption == day(dueDate: toDo.dueDate) {
                    switch dayOption {
                    case "Today":
                        newIndexPath = IndexPath(row: todayToDos.count, section: 0)
                        todayToDos.append(toDo)
                    case "Yesterday":
                        newIndexPath = IndexPath(row: yesterdayToDos.count, section: 0)
                        yesterdayToDos.append(toDo)
                    case "Tomorrow":
                        newIndexPath = IndexPath(row: tomorrowToDos.count, section: 0)
                        tomorrowToDos.append(toDo)
                    case "All":
                        newIndexPath = IndexPath(row: allToDos.count, section: 0)
                        allToDos.append(toDo)
                    default:
                        preconditionFailure("No day option")
                    }
                    tableView.insertRows(at: [newIndexPath], with: .automatic)
                }
                toDos.append(toDo)
            }
            ToDo.saveToDos(toDos)
        }
    case PropertyKeys.CancelUnwind:
        if let selectedIndexPath = tableView.indexPathForSelectedRow {
            tableView.deselectRow(at: selectedIndexPath, animated: true)
        }
    default:
        preconditionFailure("No segue identified")
    }
}

Screen Recording 2020-12-27 at 8 49 07 2020-12-27 08_52_39

After a while, I created a documentation project to remember the steps to change Project's name in Xcode

Screen Shot 2020-12-27 at 9 32 53

import Foundation

enum SavingToDoResult {
    case success
    case failure(Error)
    case failureCreatingEncoder(Error)
    case failureWritingData(Error)
}

enum URLError: Error {
    case invalidURL
}

enum DataError: Error {
    case invalidData
}

enum LoadingToDoResult {
    case success([ToDo]?)
    case failureInvalidURL(URLError)
    case failureInvalidData(DataError)
}

class ToDoController {
    static let shared = ToDoController()
    static let DocumentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    static let ArchiveURL = DocumentsDirectory.appendingPathComponent("todos").appendingPathExtension("plist")
    
    func loadToDos(completion: @escaping (LoadingToDoResult) -> Void) {
        var codedToDos: Data
        do {
            codedToDos = try Data(contentsOf: ToDoController.ArchiveURL)
        } catch {
            return completion(.failureInvalidURL(.invalidURL))
        }
        do {
            let propertyListDecoder = PropertyListDecoder()
            let toDos = try propertyListDecoder.decode(Array<ToDo>.self, from: codedToDos)
            return completion(.success(toDos))
        } catch {
            return completion(.failureInvalidData(.invalidData))
        }
    }
    
    func saveToDos(toDos: [ToDo]) -> SavingToDoResult {
        let propertyListEncoder = PropertyListEncoder()
        var codedToDos: Data
        do {
            codedToDos = try propertyListEncoder.encode(toDos)
        } catch let error {
            return .failureCreatingEncoder(error)
        }
        do {
            try codedToDos.write(to: ToDoController.ArchiveURL, options: .noFileProtection)
            return .success
        } catch let error {
            return .failureWritingData(error)
        }
    }
    
    static func loadSampleToDos() -> [ToDo] {
        let todo1 = ToDo(projectName: "para antier", title: "Para antier", isComplete: false, dueDate: Date().addingTimeInterval(-2*(24*60*60)), note: "para antier")
        let todo2 = ToDo(projectName: "para ayer", title: "para ayer", isComplete: false, dueDate: Date().addingTimeInterval(-24*60*60), note: "Sample Note 2")
        let todo3 = ToDo(projectName: "para hoy", title: "Para Hoy", isComplete: false, dueDate: Date(), note: "Sample Note 3")
        let todo4 = ToDo(projectName: "para mañana", title: "Para mañana", isComplete: false, dueDate: Date().addingTimeInterval(24*60*60), note: "Sample Note 4")
        let todo5 = ToDo(projectName: "para pasado mañana", title: "para pasado mañana", isComplete: false, dueDate: Date().addingTimeInterval(2*24*60*60), note: "Sample Note 5")
        return [todo1, todo2, todo3, todo4, todo5]
    }
}

Xcode couldn't find AddButton.swift class because according to Xcode was located in another disk, fatal error because the file was in another hardisk that was unmounted, I had to look in documentations Modify Add button according to design to rewrite the code. Again, I put the solution here to document again.:

AddButton.swift file:

import UIKit
@IBDesignable

class AddButton: UIButton {
    @IBInspectable var fillColor: UIColor = UIColor.orange
    private var halfWidth: CGFloat {
        return bounds.width / 2
    }
    private var halfHeight: CGFloat {
        return bounds.height / 2
    }
    
    override func draw(_ rect: CGRect) {
        // first paint the outside ring
        let center = CGPoint(x: bounds.width/2, y: bounds.height/2)
        let radius: CGFloat = min(bounds.width/2, bounds.height/2)
        let startAngle: CGFloat = 0.0
        let endAngle: CGFloat = .pi*2
        let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
        path.lineWidth = 2.0
        fillColor.setStroke()
        path.stroke()
        // Second, it paints the plus sign
        let plusWidth: CGFloat = min(bounds.width, bounds.height)*0.6
        let halfPlusWidth = plusWidth/2
        let plusPath = UIBezierPath()
        plusPath.lineWidth = 3.0
        plusPath.move(to: CGPoint(x: halfWidth - halfPlusWidth, y: halfHeight))
        plusPath.addLine(to: CGPoint(x: halfWidth + halfPlusWidth, y: halfHeight))
        plusPath.move(to: CGPoint(x: halfWidth, y: halfHeight-halfPlusWidth))
        plusPath.addLine(to: CGPoint(x: halfWidth, y: halfHeight+halfPlusWidth))
        UIColor.black.setStroke()
        fillColor.setStroke()
        plusPath.stroke()
    }
    
    func pulsate() {
        let pulse = CASpringAnimation(keyPath: "transform.scale")
        pulse.duration = 0.5
        pulse.fromValue = 0.98
        pulse.toValue = 1.1
        pulse.initialVelocity = 1.0
        pulse.damping = 1.0
        layer.add(pulse, forKey: nil)
    }
}

Screen Shot 2021-03-23 at 20 09 20

Reset project to a version where the ToDo objects were saved in one only array. I also fixed the missing AddButton.swift file.

Screen Shot 2021-03-26 at 8 22 40

Remember to schedule new commits to rewrite important things you were doing. (ToDoController, rename Project).

Follow the steps to rename projects in Xcode link

Follow the steps to rename targets, do not forget to document the new Info.plist file path to successfully change the target's name.

In this swift file, you are going to create an object that is in charge of share a single instance of the object. Load and save ToDo objects.

import Foundation

enum SavingToDoResult {
    case success
    case failure(Error)
    case failureCreatingEncoder(Error)
    case failureWritingData(Error)
}

enum URLError: Error {
    case invalidURL
}

enum DataError: Error {
    case invalidData
}

enum LoadingToDoResult {
    case success([ToDo]?)
    case failureInvalidURL(URLError)
    case failureInvalidData(DataError)
}

class ToDoController {
    static let shared = ToDoController()
    static let DocumentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    static let ArchiveURL = DocumentsDirectory.appendingPathComponent("todos").appendingPathExtension("plist")
    
    func loadToDos(completion: @escaping (LoadingToDoResult) -> Void) {
        var codedToDos: Data
        do {
            codedToDos = try Data(contentsOf: ToDoController.ArchiveURL)
        } catch {
            return completion(.failureInvalidURL(.invalidURL))
        }
        do {
            let propertyListDecoder = PropertyListDecoder()
            let toDos = try propertyListDecoder.decode(Array<ToDo>.self, from: codedToDos)
            return completion(.success(toDos))
        } catch {
            return completion(.failureInvalidData(.invalidData))
        }
    }
    
    func saveToDos(toDos: [ToDo]) -> SavingToDoResult {
        let propertyListEncoder = PropertyListEncoder()
        var codedToDos: Data
        do {
            codedToDos = try propertyListEncoder.encode(toDos)
        } catch let error {
            return .failureCreatingEncoder(error)
        }
        do {
            try codedToDos.write(to: ToDoController.ArchiveURL, options: .noFileProtection)
            return .success
        } catch let error {
            return .failureWritingData(error)
        }
    }
    
    static func loadSampleToDos() -> [ToDo] {
        let todo1 = ToDo(projectName: "para antier", title: "Para antier", isComplete: false, dueDate: Date().addingTimeInterval(-2*(24*60*60)), note: "para antier")
        let todo2 = ToDo(projectName: "para ayer", title: "para ayer", isComplete: false, dueDate: Date().addingTimeInterval(-24*60*60), note: "Sample Note 2")
        let todo3 = ToDo(projectName: "para hoy", title: "Para Hoy", isComplete: false, dueDate: Date(), note: "Sample Note 3")
        let todo4 = ToDo(projectName: "para mañana", title: "Para mañana", isComplete: false, dueDate: Date().addingTimeInterval(24*60*60), note: "Sample Note 4")
        let todo5 = ToDo(projectName: "para pasado mañana", title: "para pasado mañana", isComplete: false, dueDate: Date().addingTimeInterval(2*24*60*60), note: "Sample Note 5")
        return [todo1, todo2, todo3, todo4, todo5]
    }
}

After learn how to use enumerations, I am able to use them in ToDoViewController.swift file. First I had to modify load and save toDos methods:

First version of load toDos:

func prototypeMark1LoadToDos(completion: @escaping (LoadToDoResult) -> Void) {
    var codedToDos: Data
    do {
        codedToDos = try Data(contentsOf: ToDoController.ArchiveURL)
    } catch let error {
        return completion(.failureInvalidURL(error as! URLError))
    }
    let propertyListDecoder = PropertyListDecoder()
    if let toDos = try? propertyListDecoder.decode(Array<ToDo>.self, from: codedToDos) {
        return completion(.success(toDos))
    } else {
        return completion(.failureInvalidData("System couldn't decode"))
    }
}

Second version of load toDos:

func prototypeMark2LoadToDos() -> [ToDo]? {
    var codedToDos: Data
    do {
        codedToDos = try Data(contentsOf: ToDoController.ArchiveURL)
    } catch {
        return nil
    }
    do {
        let propertyListDecoder = PropertyListDecoder()
        let toDos = try propertyListDecoder.decode(Array<ToDo>.self, from: codedToDos)
        return toDos
    } catch {
        return nil
    }
}

I used first version in ToDoViewController.swift file:

First in viewDidLoad():

override func viewDidLoad() {
    tableView.delegate = self
    tableView.register(UINib(nibName: "ToDoTableViewCell", bundle: nil), forCellReuseIdentifier: "ToDoCellIdentifier")
    editBarButtonItem.title = "Edit"
    ToDoController.shared.prototypeMark1LoadToDos { (LoadToDoResult) in
        switch LoadToDoResult {
        case .success(let obteinedToDos):
            toDos = obteinedToDos
        case .failureInvalidData(let message):
            print("\(message)")
            toDos = ToDoController.loadSampleToDos()
        case .failureInvalidURL(let urlError):
            print("\(urlError.localizedDescription)")
            toDos = ToDoController.loadSampleToDos()
        }
    }
    loadCalendarView()
}

Second in delegate method tableView(_ tableView:,editingStyle:,indexPath:):

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
        let title = "Delete"
        let name = toDos[indexPath.row].title
        let messageFormatted = String(format: "delete: \(name)  ?", name)
        let alertController = UIAlertController(title: title, message: messageFormatted, preferredStyle: .actionSheet)
        let destructiveAction = UIAlertAction(title: "Delete", style: .destructive) { (_) in
            toDos.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .fade)
            switch ToDoController.shared.saveToDos(toDos: toDos) {
            case .success:
                print("success writing data after editing")
            case .failureWritingData(let error):
                print("failure writing data, \(error)", error.localizedDescription)
            case .failureCreatingEncoder(let error):
                print("failure creating encoder, \(error)", error.localizedDescription)
            }
        }
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
        alertController.addAction(destructiveAction)
        alertController.addAction(cancelAction)
        present(alertController, animated: true, completion: nil)
    }
}

Third in unwindToToDoList(segue:):

@IBAction func unwindToToDoList(segue: UIStoryboardSegue) {
    switch segue.identifier {
    case PropertyKeys.SaveUnwind:
        let sourceViewController = segue.source as! NewToDoViewController
        if let toDo = sourceViewController.toDo {
            if let selectedIndexPath = tableView.indexPathForSelectedRow {
                toDos[selectedIndexPath.row] = toDo
                tableView.reloadRows(at: [selectedIndexPath], with: .none)
                tableView.deselectRow(at: selectedIndexPath, animated: true)
            } else {
                let newIndexPath = IndexPath(row: toDos.count, section: 0)
                toDos.append(toDo)
                tableView.insertRows(at: [newIndexPath], with: .automatic)
            }
            switch ToDoController.shared.saveToDos(toDos: toDos) {
            case .success:
                print("success writing data after edit complete object")
            case .failureWritingData(let error):
                print("failure writing data, \(error)", error.localizedDescription)
            case .failureCreatingEncoder(let error):
                print("failure creating encoder, \(error)", error.localizedDescription)
            }
        }
    case PropertyKeys.CancelUnwind:
        if let selectedIndexPath = tableView.indexPathForSelectedRow {
        tableView.deselectRow(at: selectedIndexPath, animated: true)
        }
    default:
        preconditionFailure("No segue identified")
    }
}

Finally in checkmarkTapped(sender:):

func checkmarkTapped(sender: ToDoTableViewCell) {
    if let indexPath = tableView.indexPath(for: sender) {
        var toDo = toDos[indexPath.row]
        toDo.isComplete = !toDo.isComplete
        toDos[indexPath.row] = toDo
        tableView.reloadRows(at: [indexPath], with: .automatic)
        switch ToDoController.shared.saveToDos(toDos: toDos) {
        case .success:
            print("success writing data after tap checkmark")
        case .failureWritingData(let error):
            print("failure writing data, \(error)", error.localizedDescription)
        case .failureCreatingEncoder(let error):
            print("failure creating encoder, \(error)", error.localizedDescription)
        }
    }
}

After a few test, the save and load methods work correctly:

Screen Shot 2021-03-30 at 7 39 15

1, Create an enumeration with the option day:

enum OptionDay {

    case yesterday, today, tomorrow

}
  1. In order to get a filtered list of ToDo, create a function in ToDoController, this would help us to filter for each option day:
func toDosFor(optionDay: OptionDay, toDos: [ToDo]) -> [ToDo] {

    var selectedTodos: [ToDo] = []
    let calendar: Calendar = {
        var calendar = Calendar.current
        calendar.timeZone = TimeZone.current
        return calendar
    }()
    switch optionDay {
    case .today:
        for toDo in toDos {
            if calendar.isDateInToday(toDo.dueDate) {
                selectedTodos.append(toDo)
            }
        }
    case .tomorrow:
        for toDo in toDos {
            if calendar.isDateInTomorrow(toDo.dueDate) {
                selectedTodos.append(toDo)
            }
        }
    case .yesterday:
        for toDo in toDos {
            if calendar.isDateInYesterday(toDo.dueDate) {
                selectedTodos.append(toDo)
            }
        }
    }
    return selectedTodos

}
  1. Use this function right away after get the complete ToDo objects in `viewDidLoad()
override func viewDidLoad() {
    
    tableView.delegate = self
    tableView.register(UINib(nibName: "ToDoTableViewCell", bundle: nil), forCellReuseIdentifier: "ToDoCellIdentifier")
    editBarButtonItem.title = "Edit"
    ToDoController.shared.prototypeMark1LoadToDos { (LoadToDoResult) in
        switch LoadToDoResult {
        case .success(let obteinedToDos):
            self.toDos = ToDoController.shared.toDosFor(optionDay: .today, toDos: obteinedToDos) // filter ToDo
        case .failureInvalidData(let message):
            print("\(message)")
            self.toDos = ToDoController.loadSampleToDos()
        case .failureInvalidURL(let urlError):
            print("\(urlError.localizedDescription)")
            self.toDos = ToDoController.loadSampleToDos()
        }
    }
    loadCalendarView()
    
}

Build and run, the ToDo array for today will be display.

  1. In ToDoViewController declare an UISegmentedControl object and connect it to canvas. This would filter an specific day of the week, in this case we will filter today, yesterday and tomorrow.
@IBOutlet weak var optionDaySegmentedControl: UISegmentedControl!
  1. In ToDoViewController create an IBAction for the segmented control UISegmentedControl and connect it to canvas, act.
@IBAction func toDosForSelectedDay(_ sender: UISegmentedControl) {
    
    var updatedToDos = [ToDo]()
    ToDoController.shared.prototypeMark1LoadToDos { (LoadToDoResult) in
        switch LoadToDoResult {
        case .success(let obteinedToDos):
            updatedToDos = obteinedToDos
        case .failureInvalidData(let message):
            print("\(message)")
            self.toDos = ToDoController.loadSampleToDos()
        case .failureInvalidURL(let urlError):
            print("\(urlError.localizedDescription)")
            self.toDos = ToDoController.loadSampleToDos()
        }
    }
    switch sender.selectedSegmentIndex {
    case 0:
        self.toDos = ToDoController.shared.toDosFor(optionDay: .yesterday, toDos: updatedToDos)
    case 1:
        self.toDos = ToDoController.shared.toDosFor(optionDay: .today, toDos: updatedToDos)
    case 2:
        self.toDos = ToDoController.shared.toDosFor(optionDay: .tomorrow, toDos: updatedToDos)
    default:
        preconditionFailure("There is no other option")
    }
    tableView.reloadData()
    
}
  1. Update viewDidLoad to set the selected segment index, also, set a toDoList variable to get the original list of ToDos loaded from disk
override func viewDidLoad() {
    
    tableView.delegate = self
    tableView.register(UINib(nibName: "ToDoTableViewCell", bundle: nil), forCellReuseIdentifier: "ToDoCellIdentifier")
    editBarButtonItem.title = "Edit"
    ToDoController.shared.prototypeMark1LoadToDos { (LoadToDoResult) in
        switch LoadToDoResult {
        case .success(let obteinedToDos):
            self.toDoList = obteinedToDos
            self.toDos = ToDoController.shared.toDosFor(optionDay: .today, toDos: obteinedToDos) 
            self.optionDaySegmentedControl.selectedSegmentIndex = 1 // set control button to show Today ToDos
        case .failureInvalidData(let message):
            print("\(message)")
            self.toDos = ToDoController.loadSampleToDos()
        case .failureInvalidURL(let urlError):
            print("\(urlError.localizedDescription)")
            self.toDos = ToDoController.loadSampleToDos()
        }
    }
    loadCalendarView()
    
}
  1. After create a new ToDo object, create a flow to update the tableView:
@IBAction func unwindToToDoList(segue: UIStoryboardSegue) {
    
    switch segue.identifier {
    case PropertyKeys.SaveUnwind:
        let sourceViewController = segue.source as! NewToDoViewController
        if let toDo = sourceViewController.toDo {
            if let selectedIndexPath = tableView.indexPathForSelectedRow {
                toDos[selectedIndexPath.row] = toDo
                tableView.reloadRows(at: [selectedIndexPath], with: .none)
                tableView.deselectRow(at: selectedIndexPath, animated: true)
            } else {
                let newIndexPath = IndexPath(row: toDos.count, section: 0)
                toDos.append(toDo)
                // use a function that tells me if it is tomorrow, today or yesterday
                let calendar: Calendar = {
                    var calendar = Calendar.current
                    calendar.timeZone = TimeZone.current
                    return calendar
                }()
                if calendar.isDateInToday(toDo.dueDate) && optionDaySegmentedControl.selectedSegmentIndex == 1 {
                    tableView.insertRows(at: [newIndexPath], with: .automatic)
                } else if (calendar.isDateInTomorrow(toDo.dueDate) && optionDaySegmentedControl.selectedSegmentIndex == 2) {
                    tableView.insertRows(at: [newIndexPath], with: .automatic)
                } else if (calendar.isDateInYesterday(toDo.dueDate) && optionDaySegmentedControl.selectedSegmentIndex == 0) {
                    tableView.insertRows(at: [newIndexPath], with: .automatic)
                }
            }
            toDoList.append(toDo) // update the first todo list
            switch ToDoController.shared.saveToDos(toDos: toDoList) { // save the original todo list
            case .success:
                print("success writing data after edit complete object")
            case .failureWritingData(let error):
                print("failure writing data, \(error)", error.localizedDescription)
            case .failureCreatingEncoder(let error):
                print("failure creating encoder, \(error)", error.localizedDescription)
            }
        }
    case PropertyKeys.CancelUnwind:
        if let selectedIndexPath = tableView.indexPathForSelectedRow {
        tableView.deselectRow(at: selectedIndexPath, animated: true)
        }
    default:
        preconditionFailure("No segue identified")
    }
}

It has several issues right now, but it the target is accomplished. Create new commits.

  1. Update ToDo.swift
struct ToDo: Codable {
    
    var projectName: String
    var title: String
    var isComplete: Bool
    var dueDate: Date
    var startDate: Date?
    var finishDate: Date?
    var note: String?
    
    init(projectName: String, title: String, isComplete: Bool, dueDate: Date, note: String?)
    {
        self.projectName = projectName
        self.title = title
        self.isComplete = isComplete
        self.dueDate = dueDate
        self.note = note
    }
    
    static let dueDateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .short
        formatter.timeStyle = .short
        return formatter
    }()
    
    func categorizedDay() -> OptionDay {
        
        let calendar: Calendar = {
            var calendar = Calendar.current
            calendar.timeZone = TimeZone.current
            return calendar
        }()
        if calendar.isDateInToday(self.dueDate) {
            return .today
        } else if calendar.isDateInTomorrow(self.dueDate) {
            return .tomorrow
        } else if calendar.isDateInYesterday(self.dueDate) {
            return .yesterday
        } else {
            return .otherTime
        }
        
    }
}
  1. update ToDoController.swift:
import Foundation

enum SaveToDoResult {
    case success
    case failureCreatingEncoder(Error)
    case failureWritingData(Error)
}

enum URLError: Error {
    case invalidURL
}

enum DataError: Error {
    case invalidData
}

enum LoadToDoResult {
    case success([ToDo])
    case failureInvalidURL(URLError)
    case failureInvalidData(String)
}

class ToDoController {
    
    static let shared = ToDoController()
    static let DocumentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    static let ArchiveURL = DocumentsDirectory.appendingPathComponent("todos").appendingPathExtension("plist")
    
    func prototypeMark1LoadToDos(completion: @escaping (LoadToDoResult) -> Void) {
        var codedToDos: Data
        do {
            codedToDos = try Data(contentsOf: ToDoController.ArchiveURL)
        } catch let error {
            return completion(.failureInvalidURL(error as! URLError))
        }
        let propertyListDecoder = PropertyListDecoder()
        if let toDos = try? propertyListDecoder.decode(Array<ToDo>.self, from: codedToDos) {
            return completion(.success(toDos))
        } else {
            return completion(.failureInvalidData("System couldn't decode"))
        }
    }
    
    func prototypeMark2LoadToDos() -> [ToDo]? {
        var codedToDos: Data
        do {
            codedToDos = try Data(contentsOf: ToDoController.ArchiveURL)
        } catch {
            return nil
        }
        do {
            let propertyListDecoder = PropertyListDecoder()
            let toDos = try propertyListDecoder.decode(Array<ToDo>.self, from: codedToDos)
            return toDos
        } catch {
            return nil
        }
    }
    
    func saveToDos(toDos: [ToDo]) -> SaveToDoResult {
        let propertyListEncoder = PropertyListEncoder()
        var codedToDos: Data
        do {
            codedToDos = try propertyListEncoder.encode(toDos)
        } catch let error {
            return .failureCreatingEncoder(error)
        }
        do {
            try codedToDos.write(to: ToDoController.ArchiveURL, options: .noFileProtection)
            return .success
        } catch let error {
            return .failureWritingData(error)
        }
    }
    
    static func loadSampleToDos() -> [ToDo] {
        let todo1 = ToDo(projectName: "para antier", title: "Para antier", isComplete: false, dueDate: Date().addingTimeInterval(-2*(24*60*60)), note: "para antier")
        let todo2 = ToDo(projectName: "para ayer", title: "para ayer", isComplete: false, dueDate: Date().addingTimeInterval(-24*60*60), note: "Sample Note 2")
        let todo3 = ToDo(projectName: "para hoy", title: "Para Hoy", isComplete: false, dueDate: Date(), note: "Sample Note 3")
        let todo4 = ToDo(projectName: "para mañana", title: "Para mañana", isComplete: false, dueDate: Date().addingTimeInterval(24*60*60), note: "Sample Note 4")
        let todo5 = ToDo(projectName: "para pasado mañana", title: "para pasado mañana", isComplete: false, dueDate: Date().addingTimeInterval(2*24*60*60), note: "Sample Note 5")
        return [todo1, todo2, todo3, todo4, todo5]
    }
    
    func toDosFor(optionDay: OptionDay, toDos: [ToDo]) -> [ToDo] {
    
        var selectedTodos = [ToDo]()
        let calendar: Calendar = {
            var calendar = Calendar.current
            calendar.timeZone = TimeZone.current
            return calendar
        }()
        switch optionDay {
        case .today:
            for toDo in toDos {
                if calendar.isDateInToday(toDo.dueDate) {
                    selectedTodos.append(toDo)
                }
            }
        case .tomorrow:
            for toDo in toDos {
                if calendar.isDateInTomorrow(toDo.dueDate) {
                    selectedTodos.append(toDo)
                }
            }
        case .yesterday:
            for toDo in toDos {
                if calendar.isDateInYesterday(toDo.dueDate) {
                    selectedTodos.append(toDo)
                }
            }
        case .otherTime:
            for toDo in toDos {
                if !calendar.isDateInYesterday(toDo.dueDate) && !calendar.isDateInToday(toDo.dueDate) && !calendar.isDateInTomorrow(toDo.dueDate) {
                    selectedTodos.append(toDo)
                }
            }
        }
        return selectedTodos
    }
}
  1. Update ToDoViewController.swift:
enum OptionDay {

    case yesterday, today, tomorrow, otherTime

}

class ToDoViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, ToDoTableViewCellDelegate {
    
    @IBOutlet weak var tableView: UITableView!
    struct PropertyKeys {
        static let ToDoCellIdentifier = "ToDoCellIdentifier"
        static let ShowDetailsToDo = "ShowDetailsToDo"
        static let SaveUnwind = "SaveUnwind"
        static let CancelUnwind = "CancelUnwind"
    }
    @IBOutlet weak var editBarButtonItem: UIBarButtonItem!
    @IBOutlet weak var calendarView: UIView!
    var optionDay: OptionDay = .today
    var currentToDos = [ToDo]()
    var yesterdayToDos = [ToDo]()
    var todayToDos = [ToDo]()
    var tomorrowToDos = [ToDo]()
    var otherTimeToDos = [ToDo]()
    @IBOutlet weak var optionDaySegmentedControl: UISegmentedControl!

    override func viewDidLoad() {
        
        tableView.delegate = self
        tableView.register(UINib(nibName: "ToDoTableViewCell", bundle: nil), forCellReuseIdentifier: "ToDoCellIdentifier")
        editBarButtonItem.title = "Edit"
        loadToDos()
        loadCalendarView()
        
    }
    
    func loadToDos() {
        ToDoController.shared.prototypeMark1LoadToDos { (LoadToDoResult) in
            switch LoadToDoResult {
            case .success(let obteinedToDos):
                self.currentToDos = obteinedToDos
                self.todayToDos = ToDoController.shared.toDosFor(optionDay: .today, toDos: obteinedToDos)
                self.optionDaySegmentedControl.selectedSegmentIndex = 1
            case .failureInvalidData(let message):
                print("\(message)")
                self.currentToDos = ToDoController.loadSampleToDos()
            case .failureInvalidURL(let urlError):
                print("\(urlError.localizedDescription)")
                self.currentToDos = ToDoController.loadSampleToDos()
            }
        }
    }
    
    func loadCalendarView() {
        
        Bundle.main.loadNibNamed("CalendarView", owner: self, options: nil)
        calendarView.frame.origin = CGPoint(x: 0, y: 55)
        view.addSubview(calendarView)
        
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        switch optionDaySegmentedControl.selectedSegmentIndex {
        case 0:
            return yesterdayToDos.count
        case 1:
            return todayToDos.count
        case 2:
            return tomorrowToDos.count
        case 3:
            return otherTimeToDos.count
        default:
            preconditionFailure("unrecognize case")
        }
        
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        guard let cell = tableView.dequeueReusableCell(withIdentifier: PropertyKeys.ToDoCellIdentifier) as? ToDoTableViewCell else { fatalError("Could not dequeue a cell") }
        var toDo: ToDo
        switch optionDaySegmentedControl.selectedSegmentIndex {
        case 0:
            toDo = yesterdayToDos[indexPath.row]
        case 1:
            toDo = todayToDos[indexPath.row]
        case 2:
            toDo = tomorrowToDos[indexPath.row]
        case 3:
            toDo = otherTimeToDos[indexPath.row]
        default:
            preconditionFailure("unrecognize case")
        }
        cell.titleTextField?.text = toDo.title
        cell.isCompleteButton.isSelected = toDo.isComplete
        cell.delegate = self
        return cell
        
    }
    
    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        
        return true
        
    }
    
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        
        if editingStyle == .delete {
            let title = "Delete"
            var name: String
            switch optionDaySegmentedControl.selectedSegmentIndex {
            case 0:
                name = yesterdayToDos[indexPath.row].title
            case 1:
                name = todayToDos[indexPath.row].title
            case 2:
                name = tomorrowToDos[indexPath.row].title
            case 3:
                name = otherTimeToDos[indexPath.row].title
            default:
                preconditionFailure("unrecognize case")
            }
            let messageFormatted = String(format: "delete: \(name)  ?", name)
            let alertController = UIAlertController(title: title, message: messageFormatted, preferredStyle: .actionSheet)
            let destructiveAction = UIAlertAction(title: "Delete", style: .destructive) { (_) in
                switch self.optionDaySegmentedControl.selectedSegmentIndex {
                case 0:
                    self.yesterdayToDos.remove(at: indexPath.row)
                case 1:
                    self.todayToDos.remove(at: indexPath.row)
                case 2:
                    self.tomorrowToDos.remove(at: indexPath.row)
                case 3:
                    self.otherTimeToDos.remove(at: indexPath.row)
                default:
                    preconditionFailure("unrecognize case")
                }
                tableView.deleteRows(at: [indexPath], with: .fade)
                self.saveCurrentToDos()
            }
            let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
            alertController.addAction(destructiveAction)
            alertController.addAction(cancelAction)
            present(alertController, animated: true, completion: nil)
        }
    }
    
    func saveCurrentToDos() {
        self.currentToDos.removeAll()
        self.currentToDos = self.yesterdayToDos
        self.currentToDos.append(contentsOf: self.todayToDos)
        self.currentToDos.append(contentsOf: self.tomorrowToDos)
        self.currentToDos.append(contentsOf: self.otherTimeToDos)
        switch ToDoController.shared.saveToDos(toDos: self.currentToDos) {
        case .success:
            print("success writing data after editing")
        case .failureWritingData(let error):
            print("failure writing data, \(error)", error.localizedDescription)
        case .failureCreatingEncoder(let error):
            print("failure creating encoder, \(error)", error.localizedDescription)
        }
    }
    
    @IBAction func unwindToToDoList(segue: UIStoryboardSegue) {
        
        switch segue.identifier {
        case PropertyKeys.SaveUnwind:
            let sourceViewController = segue.source as! NewToDoViewController
            if let toDo = sourceViewController.toDo {
                if let selectedIndexPath = tableView.indexPathForSelectedRow {
                    switch self.optionDaySegmentedControl.selectedSegmentIndex {
                    case 0:
                        yesterdayToDos[selectedIndexPath.row] = toDo
                    case 1:
                        todayToDos[selectedIndexPath.row] = toDo
                    case 2:
                        tomorrowToDos[selectedIndexPath.row] = toDo
                    case 3:
                        otherTimeToDos[selectedIndexPath.row] = toDo
                    default:
                        preconditionFailure("unrecognize case")
                    }
                    tableView.reloadRows(at: [selectedIndexPath], with: .none)
                    tableView.deselectRow(at: selectedIndexPath, animated: true)
                    self.saveCurrentToDos()
                } else {
                    var newIndexPath: IndexPath
                    let optionDay = toDo.categorizedDay()
                    switch optionDay {
                    case .yesterday:
                        newIndexPath = IndexPath(row: yesterdayToDos.count, section: 0)
                        yesterdayToDos.append(toDo)
                    case .today:
                        newIndexPath = IndexPath(row: todayToDos.count, section: 0)
                        todayToDos.append(toDo)
                    case .tomorrow:
                        newIndexPath = IndexPath(row: tomorrowToDos.count, section: 0)
                        tomorrowToDos.append(toDo)
                    case .otherTime:
                        newIndexPath = IndexPath(row: otherTimeToDos.count, section: 0)
                        otherTimeToDos.append(toDo)
                    }
                    if toDo.categorizedDay() == .yesterday && optionDaySegmentedControl.selectedSegmentIndex == 0 {
                        tableView.insertRows(at: [newIndexPath], with: .automatic)
                    } else if toDo.categorizedDay() == .today && optionDaySegmentedControl.selectedSegmentIndex == 1 {
                        tableView.insertRows(at: [newIndexPath], with: .automatic)
                    } else if toDo.categorizedDay() == .tomorrow && optionDaySegmentedControl.selectedSegmentIndex == 2 {
                        tableView.insertRows(at: [newIndexPath], with: .automatic)
                    } else if toDo.categorizedDay() == .otherTime && optionDaySegmentedControl.selectedSegmentIndex == 3 {
                        tableView.insertRows(at: [newIndexPath], with: .automatic)
                    }
                }
                self.saveCurrentToDos()
            }
        case PropertyKeys.CancelUnwind:
            if let selectedIndexPath = tableView.indexPathForSelectedRow {
                tableView.deselectRow(at: selectedIndexPath, animated: true)
            }
        default:
            preconditionFailure("No segue identified")
        }
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == PropertyKeys.ShowDetailsToDo {
            let toDoViewController = segue.destination as! NewToDoViewController
            let selectedIndexPath = tableView.indexPathForSelectedRow!
            var selectedToDo: ToDo
            switch optionDaySegmentedControl.selectedSegmentIndex {
            case 0:
                selectedToDo = yesterdayToDos[selectedIndexPath.row]
            case 1:
                selectedToDo = todayToDos[selectedIndexPath.row]
            case 2:
                selectedToDo = tomorrowToDos[selectedIndexPath.row]
            case 3:
                selectedToDo = otherTimeToDos[selectedIndexPath.row]
            default:
                preconditionFailure("unricognized case")
            }
            toDoViewController.toDo = selectedToDo
        }
    }
    
    // MARK: Conform the Protocol
    func checkmarkTapped(sender: ToDoTableViewCell) {
        if let indexPath = tableView.indexPath(for: sender) {
            var toDo: ToDo
            switch optionDaySegmentedControl.selectedSegmentIndex {
            case 0:
                toDo = yesterdayToDos[indexPath.row]
                toDo.isComplete = !toDo.isComplete
                yesterdayToDos[indexPath.row] = toDo
            case 1:
                toDo = todayToDos[indexPath.row]
                toDo.isComplete = !toDo.isComplete
                todayToDos[indexPath.row] = toDo
            case 2:
                toDo = tomorrowToDos[indexPath.row]
                toDo.isComplete = !toDo.isComplete
                tomorrowToDos[indexPath.row] = toDo
            case 3:
                toDo = otherTimeToDos[indexPath.row]
                toDo.isComplete = !toDo.isComplete
                otherTimeToDos[indexPath.row] = toDo
            default:
                preconditionFailure("unricognize case")
            }
            tableView.reloadRows(at: [indexPath], with: .automatic)
            self.saveCurrentToDos()
        }
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        performSegue(withIdentifier: PropertyKeys.ShowDetailsToDo, sender: self)
        
    }

    @IBAction func editBarButtonItemTapped(_ sender: UIBarButtonItem) {
        
        if !tableView.isEditing {
            tableView.setEditing(true, animated: true)
            sender.title = "Done"
        } else {
            tableView.setEditing(false, animated: true)
            sender.title = "Edit"
        }
        
    }
    
    @IBAction func toDosForSelectedDay(_ sender: UISegmentedControl) {
        
        var updatedToDos = [ToDo]()
        ToDoController.shared.prototypeMark1LoadToDos { (LoadToDoResult) in
            switch LoadToDoResult {
            case .success(let obteinedToDos):
                updatedToDos = obteinedToDos
            case .failureInvalidData(let message):
                print("\(message)")
                self.currentToDos = ToDoController.loadSampleToDos()
            case .failureInvalidURL(let urlError):
                print("\(urlError.localizedDescription)")
                self.currentToDos = ToDoController.loadSampleToDos()
            }
        }
        switch sender.selectedSegmentIndex {
        case 0:
            self.yesterdayToDos = ToDoController.shared.toDosFor(optionDay: .yesterday, toDos: updatedToDos)
        case 1:
            self.todayToDos = ToDoController.shared.toDosFor(optionDay: .today, toDos: updatedToDos)
        case 2:
            self.tomorrowToDos = ToDoController.shared.toDosFor(optionDay: .tomorrow, toDos: updatedToDos)
        case 3:
            self.otherTimeToDos = ToDoController.shared.toDosFor(optionDay: .otherTime, toDos: updatedToDos)
        default:
            preconditionFailure("There is no other option")
        }
        tableView.reloadData()
        
    }
    
}

Screen Recording 2021-04-09 at 16 50 31 2021-04-09 16_55_22

This is the Ugly piece of code:

@IBAction func unwindToToDoList(segue: UIStoryboardSegue) {
    
    switch segue.identifier {
    case PropertyKeys.SaveUnwind:
        let sourceViewController = segue.source as! NewToDoViewController
        if let toDo = sourceViewController.toDo {
            if let selectedIndexPath = tableView.indexPathForSelectedRow {
                switch self.optionDaySegmentedControl.selectedSegmentIndex {
                case 0:
                    yesterdayToDos[selectedIndexPath.row] = toDo
                case 1:
                    todayToDos[selectedIndexPath.row] = toDo
                case 2:
                    tomorrowToDos[selectedIndexPath.row] = toDo
                case 3:
                    otherTimeToDos[selectedIndexPath.row] = toDo
                default:
                    preconditionFailure("unrecognize case")
                }
                tableView.reloadRows(at: [selectedIndexPath], with: .none)
                tableView.deselectRow(at: selectedIndexPath, animated: true)
                self.saveCurrentToDos()
            } else {
                var newIndexPath: IndexPath
                let optionDay = toDo.categorizedDay()
                switch optionDay {
                case .yesterday:
                    newIndexPath = IndexPath(row: yesterdayToDos.count, section: 0)
                    yesterdayToDos.append(toDo)
                case .today:
                    newIndexPath = IndexPath(row: todayToDos.count, section: 0)
                    todayToDos.append(toDo)
                case .tomorrow:
                    newIndexPath = IndexPath(row: tomorrowToDos.count, section: 0)
                    tomorrowToDos.append(toDo)
                case .otherTime:
                    newIndexPath = IndexPath(row: otherTimeToDos.count, section: 0)
                    otherTimeToDos.append(toDo)
                }
                if toDo.categorizedDay() == .yesterday && optionDaySegmentedControl.selectedSegmentIndex == 0 {
                    tableView.insertRows(at: [newIndexPath], with: .automatic)
                } else if toDo.categorizedDay() == .today && optionDaySegmentedControl.selectedSegmentIndex == 1 {
                    tableView.insertRows(at: [newIndexPath], with: .automatic)
                } else if toDo.categorizedDay() == .tomorrow && optionDaySegmentedControl.selectedSegmentIndex == 2 {
                    tableView.insertRows(at: [newIndexPath], with: .automatic)
                } else if toDo.categorizedDay() == .otherTime && optionDaySegmentedControl.selectedSegmentIndex == 3 {
                    tableView.insertRows(at: [newIndexPath], with: .automatic)
                }
            }
            self.saveCurrentToDos()
        }
    case PropertyKeys.CancelUnwind:
        if let selectedIndexPath = tableView.indexPathForSelectedRow {
            tableView.deselectRow(at: selectedIndexPath, animated: true)
        }
    default:
        preconditionFailure("No segue identified")
    }

}

This is the improvement:

@IBAction func unwindToToDoList(segue: UIStoryboardSegue) {
    
    switch segue.identifier {
    case PropertyKeys.SaveUnwind:
        let sourceViewController = segue.source as! NewToDoViewController
        if let toDo = sourceViewController.toDo {
            if let selectedIndexPath = tableView.indexPathForSelectedRow {
                switch self.optionDaySegmentedControl.selectedSegmentIndex {
                case 0:
                    yesterdayToDos[selectedIndexPath.row] = toDo
                case 1:
                    todayToDos[selectedIndexPath.row] = toDo
                case 2:
                    tomorrowToDos[selectedIndexPath.row] = toDo
                case 3:
                    otherTimeToDos[selectedIndexPath.row] = toDo
                default:
                    preconditionFailure("unrecognize case")
                }
                tableView.reloadRows(at: [selectedIndexPath], with: .none)
                tableView.deselectRow(at: selectedIndexPath, animated: true)
                self.saveCurrentToDos()
            } else {
                var newIndexPath: IndexPath
                let optionDay = toDo.categorizedDay()
                switch optionDay {
                case .yesterday:
                    newIndexPath = IndexPath(row: yesterdayToDos.count, section: 0)
                    yesterdayToDos.append(toDo)
                    if optionDaySegmentedControl.selectedSegmentIndex == 0 {
                        tableView.insertRows(at: [newIndexPath], with: .automatic)
                    }
                case .today:
                    newIndexPath = IndexPath(row: todayToDos.count, section: 0)
                    todayToDos.append(toDo)
                    if optionDaySegmentedControl.selectedSegmentIndex == 1 {
                        tableView.insertRows(at: [newIndexPath], with: .automatic)
                    }
                case .tomorrow:
                    newIndexPath = IndexPath(row: tomorrowToDos.count, section: 0)
                    tomorrowToDos.append(toDo)
                    if optionDaySegmentedControl.selectedSegmentIndex == 2  {
                        tableView.insertRows(at: [newIndexPath], with: .automatic)
                    }
                case .otherTime:
                    newIndexPath = IndexPath(row: otherTimeToDos.count, section: 0)
                    otherTimeToDos.append(toDo)
                    if optionDaySegmentedControl.selectedSegmentIndex == 3 {
                        tableView.insertRows(at: [newIndexPath], with: .automatic)
                    }
                }
            }
            self.saveCurrentToDos()
        }
    case PropertyKeys.CancelUnwind:
        if let selectedIndexPath = tableView.indexPathForSelectedRow {
            tableView.deselectRow(at: selectedIndexPath, animated: true)
        }
    default:
        preconditionFailure("No segue identified")
    }
    
}

As you can see, once you get the new toDo object and append it to the correct array, you insert the row into the correct IndexPath if is selected the corresponding segment.

  1. Remove startButton (it is redundant to have it) from ToDoTableViewCell.xib file.
  2. remove constrains of each subview, (textfield and buttons).
  3. Construct the new constraints for checkBox button from ToDoTableViewCell.xib file and remove IBOutlet button from ToDoTableViewCell.swift file

Screen Shot 2021-04-14 at 23 08 03

Screen Shot 2021-04-14 at 22 46 09

  1. Find viewDidLoad()'s NewToDoViewController method, if you are going to create a new ToDo, make titleTextField becone first responder.
override func viewDidLoad() {
    
    super.viewDidLoad()
    if let toDo = toDo { // if you inject a ToDo
        navigationItem.title = "To-Do"
        titleTextField.text = toDo.title
        isCompletButton.isSelected = toDo.isComplete
        dueDatePickerView.date = toDo.dueDate
        notesTextView.text = toDo.note
    } else { // if you don't inject a ToDo
        dueDatePickerView.date = Date().addingTimeInterval(24*60*60)
        dueDateLabel.textColor = .gray
        titleTextField.becomeFirstResponder()
    }
    updateDueDateLabel(date: dueDatePickerView.date)
    updateSaveButtonState()
    
}

RPReplay_Final1618460691 2021-04-14 23_27_36

According to Human Interface Guidelines, avoid using a segmented control in a toolbar. I will move the segmented control outside.

Screen Shot 2021-04-20 at 16 11 19

Screen Shot 2021-04-20 at 16 34 19

Screen Recording 2021-04-20 at 16 23 04 2021-04-20 16_31_00

About

This README file only pretends to be a presentation for the real project.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published