Utilize Combine for Search Engine in Swift

Implementing Combine for Search Case in Swift

Combine, is a new framework introduced by Apple since SwiftUI 1.0 back then in 2019. The architecture behind the scene itself is adopting the system of FRP (Functional Reactive Programming) which takes computation as their main computation of process combine with the propagation of data through the stream of the function. In this tutorial, I would like to explain how Combine can make your job done quicker than ever compare with the conventional way of code. Multiple-use cases of Combine usage have been explored such as Networking and any asynchronous process, but there are few of them explaining how we can utilize it for a property, especially an Observable property with a special @Published property wrapper. One of them is how do we do search operations easier with Combine. Let take a look at the old implementation below.

  1. The SearchView need a completion and we need to send the value back from onChange method of the TextField
  2. From the main view, we need to call updateSearch of the viewmodel in order to filter the data based on the updated string
  3. It’s so inconvenient to do this every time we have a search bar
 1final class ViewModel: ObservableObject {
 2  @Published var searchTerm: String = ""
 3  private var cancellables = Set<AnyCancellable>()
 4
 5  func updateSearch(text: String) {
 6    // Do data filtering
 7  }
 8}
 9
10struct SearchView: View {
11
12    @Binding var searchTerm: String
13
14    var onCompletion: (_ text: String) -> Void
15
16    var body: some View {
17        HStack {
18            Spacer()
19            Image(systemName: "magnifyingglass")
20
21            TextField("Search", text: self.$searchTerm)
22                .foregroundColor(Color.primary)
23                .padding(10)
24                .onChange(of: searchTerm, perform: { value in
25                    onCompletion(value)
26                })
27            Spacer()
28        }.foregroundColor(.secondary)
29            .background(Color(.secondarySystemBackground))
30            .cornerRadius(10)
31            .padding(10)
32    }
33}

With Combine, now we don’t need to have the completion handler on the SearchView and we can directly use.sinkoperator on the Published property. Look at the below implementation. What we can do is only do the binding to the property. The view can directly inject the property, and don’t need to do any completion handler anymore (we can remove it from the View).

  1. Do .receive(on: RunLoop.main) for receiving the operation on the main queue so the UI will not get blocked. However, you can remove this since we never use any other thread when doing the search
  2. .sink method will emit an output of the string whenever the string has a new value from the search operation.
  3. Tips : You can debounce your operation in Combine and put it in any other thread, beware do the number 1 above if you are doing that
 1final class ViewModel: ObservableObject {
 2  @Published var searchTerm: String = ""
 3  private var cancellables = Set<AnyCancellable>()
 4
 5  init() {
 6    bindingStock()
 7  }
 8
 9  func bindingStock() {
10      $searchTerm
11          .receive(on: RunLoop.main)
12          .sink(receiveValue: { [weak self] str in
13              // Do filtering data here
14          })
15          .store(in: &cancellables)
16  }
17}
18
19struct SearchView: View {
20
21    @Binding var searchTerm: String
22
23    var body: some View {
24        HStack {
25            Spacer()
26            Image(systemName: "magnifyingglass")
27
28            TextField("Search", text: self.$searchTerm)
29                .foregroundColor(Color.primary)
30                .padding(10)
31            Spacer()
32        }.foregroundColor(.secondary)
33            .background(Color(.secondarySystemBackground))
34            .cornerRadius(10)
35            .padding(10)
36    }
37}

Pretty neat right? We can achieve the search operation quicker than before with a more simplified syntax. Have fun and keep learning!