Deleting tasks & adding a Search Bar

Deleting To-Do items 🗑

Implementing this logic is very simple. Just open your TasksDoneView and apply the .onDelete modifier to the ForEach loop performing a function called removeItems.

List {
    ForEach(fetchedItems, id: \.self) { item in
        //...
    }
        .onDelete(perform: removeItems)
}

We are now supposed to implement the removeItems function. Insert the following function below the TaskDoneView’s body:

private func removeItems(at offsets: IndexSet) {
    for index in offsets {
        let item = fetchedItems[index]
        viewContext.delete(item)
    }
}

This function accepts a set of indices (since multiple items can be deleted at once, as we will see in a moment). Fortunately, this set is automatically provided by the .onDelete modifier. Thus, we don’t have to take care of picking the right ToDoItems out of our fetchedItems.

The function then cycles through every index in this set of indices and finds the corresponding ToDoItem in the fetchedItems. Then, we call the delete method of our viewContext to delete the corresponding ToDoItem(s). After this is done, we have to save these changes:

private func removeItems(at offsets: IndexSet) {
        for index in offsets {
            let item = fetchedItems[index]
            viewContext.delete(item)
        }
        do {
            try viewContext.save()
        } catch {
            print(error.localizedDescription)
        }
    }

That’s all! Run the app in the simulator and swipe a row inside the TasksDoneView. We are now able to delete specific tasks from the persistent storage!

We can also add a Button to our TasksDoneView’s navigation bar that toggles the option to delete multiple items at once. 

Implementing this is super easy; just insert the following code below the .navigationTitle modifier:

.navigationBarItems(trailing: EditButton())

When you’re now running the app, you can tap the “Edit” Button to delete specific to-do’s.

Next, we add a search bar to our app in order to let the user search for certain tasks.

Adding a Search Bar 🔎

The search bar allows the user to quickly find the right task. This is especially useful if the user has already added many to-do’s. 

Since iOS 15, SwiftUI offer us a native search bar that we can use for our apps. If you want to learn how to create your own fully customizable search bar, check out the old version of this section.

First, we need to add a State property to our TasksView for holding the text of the search query.

@State var searchText = ""

Next, we need to add the .searchable modifier to our List and bind it to the searchText State.

List {
    //...
}
    .searchable(text: $searchText)
    //...

Let’s start a Live preview and drag the list down. A Search Bar appears which we can use as a normal TextField. 

Until now the Search Bar is still without function. We need to reduce the displayed tasks in the TasksView to those that match the searchText. To do this, we simply go to our fetchResults property and return only all the ToDoItem instances contained in the fetchedItems if the searchText is empty.

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

However, if the user has entered something in the Search Bar, we return only the ToDoItems whose starting characters match the searchText.

var fetchResults: [ToDoItem] {
   if searchText.isEmpty {
       var results = [ToDoItem]()
       for item in fetchedItems {
           results.append(item)
       }
       return results
    } else {
        var filteredResults = [ToDoItem]()
            
        for searchResult in fetchedItems {
           if searchResult.taskTitle!.hasPrefix(searchText) {
               filteredResults.append(searchResult)
           }
       }
       return filteredResults
   }
}

If we now run our app, we see that the fetchResults displayed by the ForEach loop in the list are matched to the text in the Search Bar.

Conclusion 🎊

Wow, we’ve learned a lot so far!

We discovered how the CoreData framework interacts with SwiftUI and how we can fetch and save objects using our own data model.

We learned what the viewContext is, how it gets initialized by the context property of the PersistenceController, and how we can access it using the @Environment property wrapper and .environment modifier. We also learned how to filter To-Do’s by using the “predicate” argument of the @FetchRequest property wrapper.

We saw how easy it is to delete to-do items using the delete method of the viewContextcombined with the .onDelete modifier of the ForEach loop.

Finally, we learned how to let the user filter his tasks by providing a search bar. 

You are now able to store data persistently by using the CoreData framework within your SwiftUI projects!

2 replies on “Deleting tasks & adding a Search Bar”

Can you explain:
– In “.onDelete(perform: removeItems)”, why do you not have to invoke removeItems with () to call a function?
– Why is removeItems(at:) declared as a private function?

Hi! Simple brackets are not necessary because the .onDelete method already expects a function to be called and the perform parameter executes it on its own without needing us the clarify this by using (). I declared removeItems(at:) as private to make sure this function can’t be called outside the TasksDoneView.

Leave a Reply

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