swiftasynchronousswiftuiasync-awaitsearchbar

Async search bar


I need to filter my model data with a search bar. I added the .searchable() property and when the search text changes I filter my objects with fuzzy matching. This takes too much time and the app lags when writing into the search box. So I want to do the searching asynchronously so that the app doesn't freeze.

I tried to do it with the onChange(of:) property and then I create a Task that runs the async function because the onChange() property doesn't allow async functions by themselves. But the app still lags.

Here is a code example of how I tried doing it:

import SwiftUI
import Fuse

struct SearchView: View {
    @EnvironmentObject var modelData: ModelData
    
    @State var searchText = ""
    @State var searchResults: [Item] = []
    @State var searchTask: Task<(), Never>? = nil
    
    let fuseSearch = Fuse()
    
    var body: some View {
        // Show search results
    }
    .searchable(text: $searchText)
    .onChange(of: searchText) { newQuery in
        // Cancel if still searching
        searchTask?.cancel()
            
        searchTask = Task {
            searchResults = await fuzzyMatch(items: modelData.items, searchText: newQuery)
        }
    }
    

    func fuzzyMatch(items: [Item], searchText: String) async -> [Item] {
        filteredItems = items.filter {
            (fuseSearch.search(searchText, in: $0.name)?.score ?? 1) < 0.25
        }
        
        return filteredItems
    }
}

I would really appreciate some help.


Solution

  • I think the main problem is debouncing as lorem ipsum mentioned before. I just tested my code and you need to call your filter method where i printed.

    In this way you will not filter for every editing textfield. You will filter after some millisecond which you may change.

    You can find more detail in this link SwiftUI Combine Debounce TextField

        struct Example: View {
    
        @State var searchText = ""
        let searchTextPublisher = PassthroughSubject<String, Never>()
           
        var body: some View {
            NavigationView {
                Text("Test")
            }
            .searchable(text: $searchText)
            .onChange(of: searchText) { searchText in
                searchTextPublisher.send(searchText)
            }
            .onReceive(
                searchTextPublisher
                    .debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
            ) { debouncedSearchText in
                print("call your filter method")
            }
        }
    }