SwiftUI – Filter data with Searchable

By | 24/05/2023

In this post, we will see how to use the searchable modifier for filtering data in a list.
We will use Core Data to manage data and, we are going to continue the project created in the post: Swift – Core Data.

The first thing that we will change in the file ContentView.swift, will be to replace the NavigationView with the new component NavigatioStack and then, we will add the searchable modifier:

[CONTENTVIEW.SWIFT]

import SwiftUI
import CoreData

struct ContentView: View {
    // with Environment we can access here to the viewContext
    @Environment(\.managedObjectContext) private var viewContext
    
    // FetchRequest is used to extract data from the persistent storage
    // In this case the entity User without any sort and any predicate
    // In sql this would be: select * from user
    @FetchRequest(entity: User.entity(), sortDescriptors: [], predicate: nil)
    // We put the result of the query above in a variable called lstUser
    private var lstUser: FetchedResults<User>
    
    func UpdateUser(inputUser: User) -> Void{
        // for the update we pass the object User selected
        objUser = inputUser
        showNewUser = true
        isUpdate = true
    }
    
    init() {
        _showNewUser = State(initialValue: false)
        _isUpdate = State(initialValue: false)
    }
    
    // variable used to show the Form for creating a new User
    @State var showNewUser: Bool
    @State private var objUser: User = User()
    @State private var isUpdate: Bool
    @State private var searchText: String = ""
    
    var body: some View {
        NavigationStack{
            List{
                // we show all users
                ForEach(lstUser) { user in
                    HStack{
                        VStack(alignment: .leading){
                            Text("\(user.name!) - \(user.surname!)")
                                .font(.headline)
                        }
                        Spacer()
                        Button(action: { self.UpdateUser(inputUser: user)}) {
                            Text("Update")
                                .foregroundColor(.blue)
                        }
                    }
                    
                }
                // delete item
                .onDelete {
                    indexSet in
                    for index in indexSet{
                        // we delete the user
                        viewContext.delete(lstUser[index])
                    }
                    do {
                        // save the operation
                        try viewContext.save()
                    } catch {
                        print(error.localizedDescription)
                    }
                }
            }
            .navigationTitle("Users")
            .navigationBarItems(trailing: Button(action: {
                    isUpdate = false
                    objUser = User()
                    showNewUser = true
                }, label: {
                    Image(systemName: "plus.circle")
                        .imageScale(.large)
                }))
            
            .sheet(isPresented: $showNewUser) {
                    NewUser(inputUser:  objUser , isUpdate: isUpdate)
                }
            // It will add a search bar.
            .searchable(text: $searchText, prompt: "Name to search")
        }
    }
}


If we run the application, the following will be the result:


Now, we will add a method called SearchByNameOrSurname used to filter the list of User by Name or Surname:

private func SearchByNameOrSurname() {
        let predicate: NSPredicate?
        
        // If there is something in the search bar, the system will create a predicate
        if !searchText.isEmpty {
            // We create two predicates: one used to search by name and the second used to 
            // search by surname
            let namePredicate = NSPredicate(format: "name CONTAINS %@", searchText)
            let surnamePredicate = NSPredicate(format: "surname CONTAINS %@", searchText)
            
            // The NSCompoundPredicate combines these two predicates using an OR condition, so the
            // results will include users that match either the name or surname field.
            predicate = NSCompoundPredicate(orPredicateWithSubpredicates: [namePredicate, surnamePredicate])
        } else {
            predicate = nil
        }
        
        // Finally, we will use the predicate to filter the list of Users
        lstUser.nsPredicate = predicate
    }


Finally, we modify the code in order to call this method when the value in the Search Bar changes:

.searchable(text: $searchText, prompt: "Name to search")
            .onChange(of: searchText, perform: { _ in
                    SearchByNameOrSurname()
                        })



We have done and now, if we run the application, the following will be the result:



[CONTENTVIEW.SWIFT]

import SwiftUI
import CoreData

struct ContentView: View {
    // with Environment we can access here to the viewContext
    @Environment(\.managedObjectContext) private var viewContext
    
    // FetchRequest is used to extract data from the persistent storage
    // In this case the entity User without any sort and any predicate
    // In sql this would be: select * from user
    @FetchRequest(entity: User.entity(), sortDescriptors: [], predicate: nil)
    // We put the result of the query above in a variable called lstUser
    private var lstUser: FetchedResults<User>
    
    func UpdateUser(inputUser: User) -> Void{
        // for the update we pass the object User selected
        objUser = inputUser
        showNewUser = true
        isUpdate = true
    }
    
    init() {
        _showNewUser = State(initialValue: false)
        _isUpdate = State(initialValue: false)
    }
    
    // variable used to show the Form for creating a new User
    @State var showNewUser: Bool
    @State private var objUser: User = User()
    @State private var isUpdate: Bool
    @State private var searchText: String = ""
    
    var body: some View {
        NavigationStack{
            List{
                // we show all users
                ForEach(lstUser) { user in
                    HStack{
                        VStack(alignment: .leading){
                            Text("\(user.name!) - \(user.surname!)")
                                .font(.headline)
                        }
                        Spacer()
                        Button(action: { self.UpdateUser(inputUser: user)}) {
                            Text("Update")
                                .foregroundColor(.blue)
                        }
                    }
                    
                }
                // delete item
                .onDelete {
                    indexSet in
                    for index in indexSet{
                        // we delete the user
                        viewContext.delete(lstUser[index])
                    }
                    do {
                        // save the operation
                        try viewContext.save()
                    } catch {
                        print(error.localizedDescription)
                    }
                }
            }
            .navigationTitle("Users")
            .navigationBarItems(trailing: Button(action: {
                    isUpdate = false
                    objUser = User()
                    showNewUser = true
                }, label: {
                    Image(systemName: "plus.circle")
                        .imageScale(.large)
                }))
            
            .sheet(isPresented: $showNewUser) {
                    NewUser(inputUser:  objUser , isUpdate: isUpdate)
                }
            .searchable(text: $searchText, prompt: "Name to search")
            .onChange(of: searchText, perform: { _ in
                SearchByNameOrSurname()
                        })
        }
    }
    
    
    private func SearchByNameOrSurname() {
        let predicate: NSPredicate?
        
        // If there is somethingin the search bar the system will create
        // a predicate
        if !searchText.isEmpty {
            // We create two predicates: one used to seacrh the name and the second
            // used to search for surname
            let namePredicate = NSPredicate(format: "name CONTAINS %@", searchText)
            let surnamePredicate = NSPredicate(format: "surname CONTAINS %@", searchText)
            
            // The NSCompoundPredicate combines these two predicates using an OR condition, so the
            // results will include users that match either the name or surname field.
            predicate = NSCompoundPredicate(orPredicateWithSubpredicates: [namePredicate, surnamePredicate])
        } else {
            predicate = nil
        }
        
        // we will use the predicate to filter the list of Users
        lstUser.nsPredicate = predicate
    }
}



Leave a Reply

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