Iteration 2: Add & Delete with Core Data
Iteration 2: Add & Delete with Core Data
Learning Goals
- Students will implement Core Data so that a users ToDos can persist
Getting Started
- Make sure you’ve completed Iteration 1: Dynamic ToDos before starting this.
Core Data
Our ToDo List app is working great now, except nothing is being saved. When we leave our app and come back, none of our ToDos are there. Core Data is going to help us fix that. Core Data allows us to save information from each time we use the app moving forward. Let’s get this little database set up!
- In the Navigation Pane of our project, select the file that has the extension
.xcdatamodeld
- this was created when you selectedUse Core Data
when you created your project. - Click
Add Entity
(an entity is just a special kind of object) at the bottom of the screen. - You can either name this Entity within the Document Outline or open the Data Model Inspector and change the name there (I already have a ToDo class and don’t want to name my database the same as my class, so I went with
ToDoCD
)
NOTE: This vocabulary might be a little confusing because we are using a database, but a database is usually dealt with by the back-end of an application. This ‘database’ is local, on the device. If you have used localStorage with JavaScript, you can compare it to this. It is stored in the browser/app but not sent anywhere, meaning no one but the one user of this one instance of the app could access the data.
Next, add 2 attributes to our ToDoCD entity
name
: type should be Stringimportant
: type should be Boolean
Finally, we need to set up some configuration for these attributes in the Utility Pane.
- For each of these attributes, de-select the
Optional
button in the Data Model Inspector - Also in the Data Model Inspector, set the
Default Value
for the important attribute toNO
Adding To Core Data
Let’s now head back to our addToDoViewController
.
Rather than creating a new ToDo object, we need to access Core Data and create a new ToDoCD object. Since we know that the code we have works, let’s just comment out all the code inside that function and re-write it.
- Under
import UIKit
at the top of the file, addimport CoreData
. - In the
addTapped
function inAddToDoViewController
, add a new ToDo Core Data object - you have to pass in an entity and a managed object context
@IBAction func addTapped(_ sender: Any) {
//this line creates a reference to AppDelegate so we can save out ToDos in Core Data
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
//context is an extension of the reference to the Core Data
let context = appDelegate.persistentContainer.viewContext
//this creates a new object in Core Data
let toDo = ToDoCD(context: context)
//these lines give the new CD object information based on what the user provided
toDo.name = titleField.text
toDo.important = importantSwitch.isOn
//This is like clicking "save"! Our new object is now safe in Core Data!
appDelegate.saveContext()
//this send the user back to the Table View Controller
navigationController?.popViewController(animated: true)
}
Let’s do a quick recap! Once we created a new ToDo Core Data object, we set its name and whether or not its important. Then we saved the context and popped the View Controller
to get back to the ToDo List (Table View).
Run the application in the simulator and make sure everything is working.
You won’t see the new ToDo in the ToDo List yet… we have to ask Core Data for it back. We’ll do that in the next section!
Fetching From Core Data
- In the
ToDoTableViewController
, delete the entirecreateToDos
function and the line containingtoDos = createToDos()
inside of theviewDidLoad
function. - Create a new function called
getToDos
. This function is going to fetch our ToDos from Core Data. - Call this function inside of
viewDidLoad
. - We need to change our
toDos
property on theToDoTableViewController
class - we now want to return an array of Core Data ToDos
var toDos : [ToDoCD] = []
- The first thing we need to do in our
getToDos
function is access Core Data (we did this previously in ourAddToDoViewController
)
func getToDos() {
if let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext {
}
}
- Now we need to fetch the ToDos from Core Data and bring them back as an array of Core Data objects
func getToDos() {
if let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext {
if let coreDataToDos = try? context.fetch(ToDoCD.fetchRequest()) as? [ToDoCD] {
toDos = coreDataToDos
tableView.reloadData()
}
}
}
These libraries from Swift are very reliant on the version you are running on, so you may have slightly different code than someone else. That’s ok. Follow the errors and work through it. You got this!
You may be getting an error in your tableView
function about a String not being unwrapped. If so, here’s how we can fix this problem. Basically, we have to unwrap the name.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
let toDo = toDos[indexPath.row]
if let name = toDo.name {
if toDo.important {
cell.textLabel?.text = "❗️" + name
} else {
cell.textLabel?.text = toDo.name
}
}
return cell
}
Right now, getToDos
is only being called in viewDidLoad
(when we first come to the page). We need to write some code so that the new ToDos will appear as Core Data is updated.
- Write a new function
viewWillAppear
and callgetToDos
inside this function (we no longer need to callgetToDos
insideviewDidLoad
, so you can delete it there)
override func viewWillAppear(_ animated: Bool) {
getToDos()
}
Deleting From Core Data
Remember earlier when we talked about removing a ToDo and that using Core Data was going to simplify this process for us and not require us to loop over an array to find a specific ToDo… here we go!
- In our
CompleteToDoViewController
, the first thing we need to do is update ourselectedToDo
property - this now needs to either be a type of Core Data ToDo ornil
var selectedToDo : ToDoCD?
Run the application in the simulator. This causes us a few errors, but no worries… We got this!!
Inside of viewDidLoad
where we are setting the text of the titleLabel to the selectedToDo.name, we can add a ?
after selectedToDo
and it will tell our code “if there is a selectedToDo, we’ll go ahead and pass it the info it needs; otherwise, we’ll set it equal to nil
”.
titleLabel.text = selectedToDo?.name
We may still have a few errors. Let’s head back over to our ToDoTableViewController
- it looks like our prepare
for segue function is mad at us. When the segue destination is CompleteToDoViewController
, our if let should now read if let toDo = sender as? ToDoCD
(instead of just ToDo). You may need to run Product -> Clean
to make sure all these changes take effect.
- Let’s now add some code to our
completeTapped
function that will delete a selected ToDo from Core Data (remember… we first need to write that same line of code that will allow us to access Core Data) and pop us back to the ToDo List.
@IBAction func completeTapped(_ sender: Any) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let context = appDelegate.persistentContainer.viewContext
if let theToDo = selectedToDo {
context.delete(theToDo)
navigationController?.popViewController(animated: true)
}
}
And now we have a fully functional ToDo List application! Great Job!
Commit Your Work
Commit your work to the Git repository. If you need to brush up on how to do that - no worries! It’s listed as Step 5 in the Git and GitHub walk-thru. Your commit message should be something like “Complete Iteration 2”.