Search code examples
iosswiftswiftuicombinedebouncing

Swift filtering the data into search Bar using Combine


I am trying to return the search result when user type into search bar . Here I am using combine approach with Swift UI . I am trying to binding the object form view model on change function but I am getting following errors.

Cannot convert value of type 'Published.Publisher' to expected argument type '(String) -> Void'

Here is my viewModel code .

    final class FruitListViewModel: FruitListViewModelType, ObservableObject {
        
        private let service: Service!
        @Published private(set) var fruits = [Fruits]()
        // @Published var filteredFruit: [Fruits] = []
        private var cancellable = Set<AnyCancellable>()
        @Published var searchText: String = ""
        
        
        init(service:Service = ServiceImpl()) {
            self.service = service
            
        }
        
        func fetchFruit() {
            let client = ServiceClient(baseUrl:EndPoints.baseUrl.rawValue, path:Path.fruitList.rawValue, params:"", method:"get")
            
            service.fetchData(client: client, type: [Fruits].self)
                .receive(on: RunLoop.main)
                .sink { completion in
                    switch completion {
                    case let .failure(error):
                        print(error)
                    default:
                        break
                    }
                } receiveValue: { response in
                    self.fruits = response
                }.store(in: &cancellable)
            
            $searchText
                .combineLatest($fruits)
                .debounce(for: .milliseconds(80), scheduler: RunLoop.main)
                .map { (searchText, fruit) -> [Fruits]  in
                    guard !searchText.isEmpty else {
                        return fruit
                    }
                    let lowercasedText = searchText.lowercased()
                    let filterFruitList = fruit.filter { (fruit) -> Bool in
                        fruit.name.lowercased().contains(lowercasedText) ||
                        fruit.genus.lowercased().contains(lowercasedText) ||
                        fruit.family.lowercased().contains(lowercasedText)
                    }
                    return filterFruitList
                }
                .sink { [weak self] (filterList) in
                    self?.fruits = filterList
                }
                .store(in: &cancellable)
        }

}

Here is the code into view ..

struct ContentView: View {

    @EnvironmentObject private var viewModel: FruitListViewModel
    @State var searchText = ""

    var body: some View {
        NavigationView {
            List {
                ForEach(viewModel.fruits) { fruit in
                    NavigationLink(destination: FruitDetailsView(fruit: fruit)) {
                        RowView(name: fruit.name, genus: fruit.genus, family: fruit.family)
                    }
                }
            }
            .searchable(text: $viewModel.searchText)
            .onChange(of: viewModel.searchText, perform: viewModel.$searchText)
            .task {
                viewModel.fetchFruit()
            }
            .navigationTitle("Fruits List")
        }
        .onAppear {

            viewModel.fetchFruit()
        }
    }

}

Here is the screenshot of the error ..

enter image description here

Search filter result .

enter image description here


Solution

  • Replace

    @Published private(set) var fruits = [Fruits]()
    // @Published var filteredFruit: [Fruits] = []
    

    with

    @Published private(set) var fruits = [Fruits]()
    @Published var filteredFruit: [Fruits] = []
    

    Replace

    .sink { [weak self] (filterList) in
        self?.fruits = filterList
    }
    

    with

    .sink { [weak self] (filterList) in
        self?.filteredFruit = filterList
    }
    

    Replace

    struct ContentView: View {
    
        @EnvironmentObject private var viewModel: FruitListViewModel
        @State var searchText = ""
       
        var body: some View {
            NavigationView {
                List {
                    ForEach(viewModel.fruits) { fruit in
                        NavigationLink(destination: FruitDetailsView(fruit: fruit)) {
                            RowView(name: fruit.name, genus: fruit.genus, family: fruit.family)
                        }
                    }
                }
                .searchable(text: $viewModel.searchText)
                .onChange(of: viewModel.searchText, perform: viewModel.$searchText)
                .task {
                    viewModel.fetchFruit()
                }
                .navigationTitle("Fruits List")
            }
            .onAppear {
    
                viewModel.fetchFruit()
            }
        }
    
    }
    

    with

    struct ContentView: View {
    
        @EnvironmentObject private var viewModel: FruitListViewModel
    
        var body: some View {
            NavigationView {
                List {
                    ForEach(viewModel.filteredFruit) { fruit in
                        NavigationLink(destination: FruitDetailsView(fruit: fruit)) {
                            RowView(name: fruit.name, genus: fruit.genus, family: fruit.family)
                        }
                    }
                }
                .searchable(text: $viewModel.searchText)
                .navigationTitle("Fruits List")
            }
            .onAppear {
                viewModel.fetchFruit()
            }
        }
    
    }
    

    And (not related) declare

    private let service: Service // no exclamation mark
    

    Side note:

    Please consolidate the fruit(s) spelling, there is no plural form with trailing s in English and also the model struct is supposed to be named Fruit.