Students will build a simple task tracking app to practice project planning, progress tracking, MVC separation, intermediate table view features, and swift delegates. Students who complete this project independently are able to:
- identify and build a simple navigation view hierarchy
- create a model object
- add staged data to a model object controller
- implement the UITableViewDataSource protocol
- implement a static UITableView
- create a custom UITableViewCell
- write a custom delegate protocol
- use a date picker as a custom input view
- wire up view controllers to model object controllers
- implement basic data persistence
Please make sure to fork and clone this project before you begin. Once you have cloned it, navigate into the repo via the terminal and switch to the starter branch with git checkout starter
. If at any point in time you need to view the master code solutions, you can switch back to the main branch.
- Create a UITableViewController file called
TaskListTableViewController
- Add a UITableViewController scene to your
Main.storybaord
that will be used to list tasks. Class it as aTaskListTableViewController
- Embed the scene in a UINavigationController and set it as your apps initial entry point
- In your
TaskListTableViewController
, set your table view's prototype style tobasic
(we will change this later to be a custom style) - Give your cell a reuseIdentifier of
taskCell
- Add an
Add
system bar button item to the navigation bar - Create a UIViewController file called
TaskDetailViewController
- Add a UIViewController scene to your
Main.storyboard
that will be used to view and/or create a task. Class it as aTaskDetailViewController
- Add a segue from the Add bar button item from the first scene to the second scene (This segue will not pass data, and therfore will not need an identifier)
- Add a segue from the prototype cell in the first scene to the second scene (This segue will pass data, so you must give it an identifier)
- In your
TaskDetailViewController
scene, and aSave
bar button item in the right side of your navigation bar - Add the following view elements and constrain them how you would like:
- UITextField (this is where the user will add the tasks name). Give the text field a placeholder text of "Enter task name..."
- UITextView (this is where the user will add the tasks notes). Replace the default text with "Enter notes here..."
- UIDatePicker (this is where the user will select the tasks due date)
- Create the following IBOutlets from the above views:
taskNameTextField
taskNotesTextView
taskDueDatePicker
- Create the following IBActions from the above views:
saveButtonTapped
dueDatePickerDateChanged
- Create a new swift file called
Task.swift
and inside that file create aTask
class - Add the following properties to your model: name (String), notes (String?), dueDate (Date?), and isComplete (Bool)
- Create your memberwise initializer and give isComplete a default value of false
Create a TaskController model object controller that will manage and serve Task objects to the rest of the application.
- Create a
TaskController.swift
file and define a newTaskController
class inside - Create a
shared
property as a shared instance - Add a
tasks
property and set it to be an array ofTask
objects, with a default value of empty - Create the following CRUD function signatures:
createTaskWith(name: String, notes: String?, dueDate: Date?)
update(task: Task, name: String, notes: String?, dueDate: Date?)
toggleIsComplete(task: Task)
delete(task: Task)
- Create your 3 persistence methods,
fileURL
,saveToPersistentStorage
,loadFromPersistentStorage
- Hint: You may reference old projects or use a code snippet to do this. However, make sure to update the necessary code.
- Hint 2: You will need to make your model codable.
- Go through each CRUD function and build out the logic. Create should create a
Task
and append it to thetasks
array. Update should update the passed in task with the new values that were passed in. ToggleIsComplete should simply flip the boolean status of a tasksisComplete
property. Delete should first find the index of the given task (Hint: You will need to implement equatable on yourTask
model for this to work) and then remove the task at that index from thetasks
array. Make sure to call your save funtion at the end of each CRUD function
- Step 6 might be tough. Use previous projects as a reference. If you are stuck for more than 20 minutes, reach out in the queue channel for support.
Go to TaskListTableViewController.swift and finish setting up your views.
- Implement the UITableViewDataSource functions using
TaskController.shared.tasks
as your source of truth - Setup your cells to display the name of the task (We will change this later to be a custom cell)
- You will want your table view to reload each time it appears, so add the override function
viewWillAppear()
to your code and set your table view to reload its data - Add swipe-to-delete support for deleting tasks from the List View
Recall that you created two segues from the List View to the Detail View. The segue from the plus button will tell the TaskDetailTableViewController that it should create a new task. The segue from a selected cell will tell the TaskDetailTableViewController that it should display a previously created task and save any changes made to it.
-
Implement the prepare(for segue: UIStoryboardSegue, sender: Any?) function. Be sure to check the identifier of the segue, get the destination of the segue, then get the index path for the selected row and use that index path to pass the selected task to
TaskDetailViewController
(Hint: To do this, you will need to go to yourTaskDetailViewController
and setup a landing pad property to receive the task) -
Make sure to call your
loadFromPersistenceStorage()
function in yourviewDidLoad()
- If you haven't already, delete any boiler-plate code, including the navigation section
- If you haven't already, create an optional landing pad property to receive a task from your
TaskListTableViewController
. (Consider naming thistask
) - Add a property,
var date: Date?
. This will be used to capture the users selected due date - Build out your
saveButtonTapped
IBAction. Make sure to pop the view controller after your code has been executed. (Note: If yourtask
landing pad is nil, this button should create a new task. If yourtask
has a value, then the save button should update your existing task. Make sure, in both instances, to pass the task name, notes, and date (from thedate
variable you created.) - In your
dueDatePickerDateChanged
IBAction, set thedate
variable you created to the date value of yourtaskDueDatePicker
Your Detail View should follow the ‘updateViews’ pattern for updating the view elements with the details of a model object.
- Add an
updateViews()
function. In the body of the function, make sure a task exists. If one does, update all the view elements to reflect the necessary data (task name, task notes, task due date)
Give your app a test run. You should be able to create a new task and then see it on your TaskListTableViewController
. You should also be able to click on a cell to see the details of a task as well as be able to update those details. If you have any issues, spend 20 minutes debugging, and if you are unable to find a resolution, send a message in the queue channel.
Build a custom table view cell to display tasks. The cell should display the task name and have a button that acts as a checkmark to display and toggle the completion status of the task.
- Add a new file called
TaskTableViewCell.swift
as a subclass of UITableViewCell (delete theawakeFromNib()
andsetSelected()
functions) - Assign the new class to the prototype cell on the
TaskListTableViewController
Scene inMain.storyboard
- Change the prototype cell's style from basic to custom and design the prototype cell with a label on the left and a square button on the right
- Note: If you are using a stack view, constrain the aspect ratio of the button to 1:1 to force the button into a square that gives the remainder of the space to the label
- note: Use the image edge inset to shrink the image to not fill the entire height of the content view, you can adjust the image edge insets in the Size Inspector of the UIButton
- Remove text from the button, but add an image of an empty checkbox
- note: Use the ‘complete’ and ‘incomplete’ image assets included in the project folder
- Create an IBOutlet for the label named
taskNameLabel
- Create an IBOutlet for the button named
completionButton
- Create an IBAction for the button named
completionButtonTapped
which you will implement using a custom protocol in the next step
- On your
TaskTableViewCell
, create an optional task variable in order for theTaskListTableViewController
to pass a specific task to the cell - Add a
didSet
property observer to the task variable that you just created (leave the body blank for now) - Add an
updateViews()
function. In the body of this function first make sure that you have a task, then assign the task's name to youtaskNameLabel
, and check you task'sisComplete
status. If your status is true, set thecompletionButton
to have a background of the complete image (in your assets). If the status is false, set it to have a background of the incomplete image. - Go back to the body of your
didSet
on yourtask
variable, and callupdateViews()
- We will come back to the
completionButtonTapped
IBAction shortly. For now, update yourcellForRowAt
data source function in yourTaskListTableViewController
to cast your cell as aTaskTableViewCell
, and then to pass a given task over to the cell.
- Step 5 is a tough one. Give it your best shot. If you cannot work it out after 20 minutes, reach out in the queue channel for assistance.
In this next section, you will write a protocol for the TaskTableViewCell
to delegate handling a button tap to the TaskListTableViewController
, adopt the protocol, and use the delegate method to mark the task as complete and reload the cell.
- Add a protocol named
TaskCompletionDelegate
to the top of yourTaskTableViewCell.swift
file (above the class) - In the body of your protocol, define a required
taskCellButtonTapped(_ sender: TaskTableViewCell)
function (keep in mind, you only need a function signature, no body)
- Hint: Don't forget to conform your protocol to
AnyObject
- Add an optional
delegate
property on theTaskTableViewCell
- Hint: remember to make this a weak var
- Update the
completionButtonTapped
IBAction to check if a delegate is assigned, and if so, call the delegate protocol function - Adopt the protocol in the
TaskListTableViewController
class
- Hint: I recommend you do this via an extension
- Implement the
taskCellButtonTapped
delegate function, guard to make sure you have a task, and then call yourtoggleIsComplete
function on yourTaskController
. After that, re-call yourupdateViews()
function on the sender. - One final step, your cell needs to know who it's delegate is. To do this, got to your
cellForRowAt
function on yourTaskListTableViewController
and assign the cell'sdelegate
property to the value ofself
.
At this point you should be able to run your project and toggle tasks directly from the tasks list. Test your app. Make sure there are no bugs. Do not forget to test persistence. If you have any issues, spend 20 minutes debugging, and then send a message via the queue channel.
Copyright © 2020 Strayer University. Unauthorized use and/or duplication of this material without express and written permission from Strayer University is strictly prohibited. To see Devmountain's privacy policy, please vistit https://devmountain.com/privacy