New to "PassthroughSubject" and I am trying to implement search using "PassthroughSubject". It worked fine but only issue being I was getting results for all the requests sent via search i.e. let’s say I try "Hello" in search bar, search for "Hello" is initiated and before it gets completed, I remove few characters i.e. I remove last "lo", leaving the text as "Hel" in search bar. Now a request for "Hel" is being made. I get results for both these requests in random order. I just want to get results of the last request and with to cancel any uncompleted/pending requests.
I read that ‘switchToLatest()’ helps with that, but when I try to use it in my code, I am getting "No exact matches in call to instance method 'switchToLatest'"
error.
Sample code:
import SwiftUI
import Foundation
import Combine
struct ContentView: View {
@State private var searchText = ""
let searchTextPublisher = PassthroughSubject<String, Never>()
var body: some View {
NavigationStack {
VStack {
SearchResultsListView(searchText: searchText, searchTextPublisher: searchTextPublisher)
.searchable(
text: $searchText,
placement: .navigationBarDrawer(displayMode: .always),
prompt: "search"
)
.onChange(of: searchText) { newText in
searchTextPublisher.send(newText) /// Publishes when a search term is changed. Used to debounce search.
}
}
}
}
}
struct SearchResultsListView: View {
var searchText: String
var searchTextPublisher: PassthroughSubject<String, Never>
var body: some View {
VStack {
// List view and some other UI/UX
}
.onReceive(searchTextPublisher.debounce(for: 0.4, scheduler: RunLoop.main)) { debouncedSearchText in
Task {
if !debouncedSearchText.isEmpty {
searchTextPublisher
.map() {_ in
Task {
await self.initiateSearch(debouncedSearchText: debouncedSearchText)
}
}
.switchToLatest()
.sink {
print("####")
}
} else {
// show empty results view
}
}
}
}
private func initiateSearch(debouncedSearchText: String) async -> AnyPublisher <[CustomObject], Never> {
await Task {
do {
let searchResults = try await getSearchResultsAsync(searchText: debouncedSearchText)
} catch {
// Show error message
}
}
.result.publisher.eraseToAnyPublisher()
}
}
What I am doing wrong here? My call to get search results i.e. "getSearchResultsAsync" is async call.
Thanks!
2
Answers
to remove the error, you have to change your
sink
to:While I doubt that mixing unstructured tasks and the combine world in the view is a good idea, or even possible.
Just take a look on this and consider decoupling the business logic from the view.
Since you are using async/await you can remove all the Combine and the
onReceive
. In SwiftUI it is.task
, e.g.