This post is going to walk through how to make a task list app that stores the task in Core Data.
This app will have the following features:
Add a task
Complete a task
Delete a task
Persist data if the app is closed
Here is what the finished app will look like
The finished app can be found in this GitHub repo:
1. Create a new single page iOS app
Create a new Xcode project for a single view iOS app.
Check the boxes for SwiftUI and to use Core Data.
2. CoreData Entities and Attributes
The first thing we need to do is add an entity to the CoreData model. To do this, open ProjectName.xcdatamodeld, where ProjectName is what you called the project in Step 1, and click on Add Entity at the bottom of the window. Name the new entity Task.
Create a new Xcode project for a single view iOS app.
Check the boxes for SwiftUI and to use Core Data.
2. CoreData Entities and Attributes
The first thing we need to do is add an entity to the CoreData model. To do this, open ProjectName.xcdatamodeld, where ProjectName is what you called the project in Step 1, and click on Add Entity at the bottom of the window. Name the new entity Task.
The image below highlights where to change the name in the Inspector.
2.1 Adding attributes to the Task Entity
Next, we need the Task entity to have attributes to store the following information:
id: used as a unique identifier for each task
name: what the user will call the task
isComplete: defines whether or not a task is completed
dateAdded: to know when a task was added
To add attributes to Task, click the + in the Attributes section, and give the attribute a name and type. The GIF below shows how to do this.
This table describes each attribute and the type associated with the attribute.
Attribute
Type
id
UUID
name
String
isComplete
Bool
dateAdded
Date
The ProjectName.xcdatamodeld should now look like the picture below.
2.2 Add a new Swift file
Now, we are adding a new swift file that will make Task identifiable making the List of tasks easier to call.
Add a new Swift file and call it Task+Extensions
In the file, add the following:
extensionTask:Identifiable{}
By adding the code above, the Task class now conforms to the Identifiable class.
2.3 Add CoreData to ContentView.swift
We need to add a variable that accesses our Managed Object Context in ContentView.swift. To do this, open ContentView.swift and add @Environment(.\managedObjectContext) var context before the body variable. ContentView should now look like this:
We declared context as an environment variable, meaning that the value is going to come from the view's environment. In this case, it is going to come from SceneDelegate.swift on lines 23 through 27 where context is declared and then given to ContentView().
3. UI Time and make it work!
We are now going to work on the UI in ContentView.swift.
3.1 Adding a TextField
Let's start by adding a TextField to the app. Change the Text(HelloWorld) to TextField(title: StringProtocol, text:Binding<String>). TextField needs two properties, a StringProtocol and a Binding<String>. For the StringProtocol, give it a property of "Task Name". When the TextField is empty, Task Name will appear in light gray.
Now, we still need a Binding<String>, this isn't as easy as TextField. We need to declare a variable for it. Before the body variable declaration, add @State private var taskName: String = "", and then make the second property of TextField$taskName. ContentView.swift should now look like this:
structContentView:View{@Environment(\.managedObjectContext)varcontext// this is the variable we added@StateprivatevartaskName:String=""varbody:someView{// this is the TextField that we addedTextField("Task Name",text:$taskName)}}structContentView_Previews:PreviewProvider{staticvarpreviews:someView{ContentView()}}
Since we are using SwiftUI, if you use the canvas, you can see what the UI looks like without having to run the app in the simulator.
What did we do?
I'm going to explain the parts of @State private var taskName: String = "" and why we needed to do this.
First, this is declaring a State property by using the @State property wrapper so that taskName is a binding value. A State property is going to store the value in taskName and allow the view to watch and update when the value changes.
3.2 Adding the task to our CoreData
First, we need to add a button so that when the user is done typing, they can then add the task to their list.
To do this, we are going to wrap TextField in an HStack and then add a Button(). When adding the button, the action should be self.addTask() and label in the button should be Text("Add Task).
Here's what the code in body should look like now.
entity declares what Core Data entity we are retrieving
sortDescriptors describes how we want to sort the entities
predicate acts as a filter
So with the code above, we are asking for all Tasks that are not completed and for those Tasks to be sorted by date newest to oldest.
Next, we need to make a list that shows the tasks. Let's embed the HStack inside a VStack. It should look like this:
VStack{HStack{// TEXTFIELD CODE HERE}}
Now, we can add a list. After the HStack, add the following:
List{Text("Hi")}
This adds a list underneath the TextField and makes the UI look like this.
Next, we are going to make "Hi" repeat for however many tasks we have. Embed Text("Hi") inside a ForEach like this:
ForEach(notCompletedTasks){taskinText("Hi")}
We did not have to specify the id for notCompletedTasks in the ForEach because Task conforms to Identifiable thanks to our work in step 2.3.
If you run the app, then put in a task name and hitting Add Task will make another row of "Hi".
Let's make a new struct for a TaskRow view that will take in the task in ContentView.swift. Above ContentView(), add the following:
structTaskRow:View{vartask:Taskvarbody:someView{Text(task.name??"No name given")}}
Inside the Text you will see that we have to use the nil-coalescing operator, ??, to give a default value. The reason we do this is because the value for the Task attributes are optional and might not have a value.
Now, inside the ForEach replace the Text with TaskRow(task). ContentView.swift should have the following code.
importSwiftUIstructTaskRow:View{vartask:Taskvarbody:someView{Text(task.name??"No name given")}}structContentView:View{@Environment(\.managedObjectContext)varcontext@FetchRequest(entity:Task.entity(),sortDescriptors:[NSSortDescriptor(keyPath:\Task.dateAdded,ascending:false)],predicate:NSPredicate(format:"isComplete == %@",NSNumber(value:false)))varnotCompletedTasks:FetchedResults<Task>@StateprivatevartaskName:String=""varbody:someView{VStack{HStack{TextField("Task Name",text:$taskName)Button(action:{self.addTask()}){Text("Add Task")}}List{ForEach(notCompletedTasks){taskinTaskRow(task:task)}}}}funcaddTask(){letnewTask=Task(context:context)newTask.id=UUID()newTask.isComplete=falsenewTask.name=taskNamenewTask.dateAdded=Date()do{trycontext.save()}catch{print(error)}}}structContentView_Previews:PreviewProvider{staticvarpreviews:someView{ContentView()}}
Here is how the app should work now.
4. Marking a task as complete!
Now, we are going to mark the task as complete, which should make the task disappear from the list.
First, we are going to embed the TaskRow into a Button and the action of the button is going to be self.updateTask(task). Now that will look like this.
Next, we need to make a function called updateTask so that we can actually update the task and mark as complete.
After addTask, let's add func updateTask(_ task: task){}. Using the _ says that we can ignore the argument label when calling the function. If you want to read more about argument labels, click here to read my post about it, next, let's add the internals of the function.
Let's dive into this a bit. The first thing we do is set a constant for the new value of isComplete for the task. Then, we set the id of the task to a constant to use in the predicate. Next, we need to create a fetch request that gets the specific task that we are updating. Then we perform the update.
Now, if you run the app, the app will allow you to add a task and then tap on it to mark as complete. Since we are only using the non-completed tasks in the list, the completed task disappears from the list. The gif below shows the final app.
Edit #1: Posted on 2019-01-09
Here's a finished project as an example if you wanted to implement a Core Data helper class for the functions addTask() and updateTask().