Marking tasks as done

Marking tasks as done ☑️

When the user taps on a specific row, we want to mark the corresponding task as done. Below the saveTask function, we create another one named markTaskAsDone. Because we need to know which object of our fetchedItems to update, this function requires an “index” parameter.

func markTaskAsDone(at index: Int) {
    
}

Let’s call this function from our Circle-Button. We get the index of the ToDoItem the user wants to mark as done by using the firstIndex method.

Button(action: {
    markTaskAsDone(at: fetchedItems.firstIndex(of: item)!)
}){
    Image(systemName: "circle")
        .imageScale(.large)
        .foregroundColor(.gray)
}

Inside the markTaskAsDone function, we can now use the passed index for finding the ToDoItem to be marked as done.

func markTaskAsDone(at index: Int) {
    let item = fetchedItems[index]
}

Great, now our markTaskAsDone function knows which item of the fetchedItems it should update.

We can now set the ToDoItem’s taskDone value to true.

func markTaskAsDone(at index: Int) {
    //...
    item.taskDone = true
}

Now, we are ready to resave our updated ToDoItem.

func markTaskAsDone(at index: Int) {
    let item = fetchedItems[index]
    item.taskDone = true
    do {
        try viewContext.save()
    } catch {
        print(error.localizedDescription)
    }
}

Let’s see if that works. When we tap on the Circle-Button of a specific row now, our markTaskAsDone function gets called with passing the right index of the specific item of the fetchedItems. The called markTaskAsDone function then updates the taskDone value and saves the corresponding ToDoItem again.

Our @FetchRequest property refetches our items, but filters out the updated ToDoItem, since its taskDone attribute is true now! Our fetchedItems property doesn’t contain the marked to-do anymore and so the item got removed from our List.



Great, we’ve learned how CoreData works in SwiftUI and how to create, fetch and update objects.

Next, we will create another main view for showing all tasks that are marked as done. We will also learn how to delete specific ToDoItems.

Creating our “TasksDone” view

To create the new view for listing the tasks that are marked as done, create a new SwiftUI file and name it TasksDoneView.

To fetch the to-do item’s from CoreData’s persistent storage, we basically follow the same steps as before. 

First, we need to have access to the viewContext. We can access it by using the @Environment property wrapper again.

struct TasksDoneView: View {
    
    @Environment(\.managedObjectContext) var viewContext
    
    var body: some View {
        Text("Hello, World!")
    }
}

We also want to provide the TasksDoneView_Previews struct with such a viewContext.

struct TasksDoneView_Previews: PreviewProvider {
    static var previews: some View {
        TasksDoneView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}

Now we can fetch the stored items by using the @FetchRequest modifier. We are using the same “entity” and “sortDescriptor” as before, but this time we only want to fetch objects where the taskDone attribute is set to true. Therefore, we write below our viewContext property:

@FetchRequest(entity: ToDoItem.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \ToDoItem.createdAt, ascending: false)], predicate: NSPredicate(format: "taskDone = %d", true), animation: .default)

We can now declare our fetchedItems variable below the @FetchRequest for holding the fetched to-do items just as we did in our TasksView.

var fetchedItems: FetchedResults<ToDoItem>

Again, we declare another variable for retrieving the ToDoItem instances from the fetchedItems.

var fetchResults: [ToDoItem] {
    var results = [ToDoItem]()
    for item in fetchedItems {
        results.append(item)
    }
    return results
}

Great! We’ve done everything necessary for retrieving the To-Do’s that the user has marked as done.

Let’s proceed with composing the interface of our TasksDone view. Again, we create a List and insert a ForEach loop. The ForEach loop should iterate through the fetched items; therefore, we write:

var body: some View {
    List {
        ForEach(fetchResults, id: \.self) { item in
            //...
        }
    }
}

For every ToDoItem inside the fetchResults, we want to show a row containing the task’s title and a checkmark symbol. To do this, insert the following code into the ForEach loop:

ForEach(fetchResults, id: \.self) { item in
    HStack {
        Text(item.taskTitle ?? "Empty")
        Spacer()
        Image(systemName: "checkmark.circle.fill")
            .imageScale(.large)
            .foregroundColor(.blue)
    }
        .frame(height: rowHeight)
}

Our TasksDoneView‘s List should have the same layout as the one in our TasksView. To achieve this, we use the .listStyle modifier again. At this point, we can also specify the TasksDoneView’s navigation bar title.

List {
    //...
}
    .navigationBarTitle(Text("Tasks done"))
    .listStyle(GroupedListStyle())

To see how the TasksDoneView will look when being stacked into a navigation view hierarchy, wrap the corresponding TasksDoneView instance of the TasksDoneView_Previews into a NavigationView.

struct TasksDoneView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            TasksDoneView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
        }
    }
}

Linking our views ⛓

To navigate to our TasksDoneView, open the ContentView.swift file and wrap the Text view reading “Tasks done” into a NavigationLink.

NavigationLink(destination: TasksDoneView()) {
    Text("Tasks done")
        .frame(height: rowHeight)
}

The whole row acts now as a button. When the user taps on the Text view, he navigates to the TasksDone view.

Try it out by running the app in the simulator.

Make sure you mark a specific task as done. This causes the corresponding ToDoItem to disappear from the TasksView, but when we navigate to the TasksDoneView, we see that this ToDoItem is now listed there!


We almost finished our To-Do app. What’s left is to let the user delete a task that is marked as completed.

5 replies on “Marking tasks as done”

Hello,
when i touch the row the task is marked as done. The touch to mark task as done is not limited to the circle button ?
The entire row react to the touch…
Any idea ?
Using Xcode 12.01
Thanx

Same behavior reproduced here. The suggestion by BLCKBIRDS Admin fixes the issue. The author might want to change the code in the tutorial.

Leave a Reply

Your email address will not be published. Required fields are marked *