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

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!

  1. In the Navigation Pane of our project, select the file that has the extension .xcdatamodeld - this was created when you selected Use Core Data when you created your project.
  2. Click Add Entity (an entity is just a special kind of object) at the bottom of the screen.
  3. 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 String
  • important: 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 to NO

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, add import CoreData.
  • In the addTapped function in AddToDoViewController, 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 entire createToDos function and the line containing toDos = createToDos() inside of the viewDidLoad 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 the ToDoTableViewController 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 our AddToDoViewController)
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 call getToDos inside this function (we no longer need to call getToDos inside viewDidLoad, 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 our selectedToDo property - this now needs to either be a type of Core Data ToDo or nil
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”.