我需要使用搜索欄過濾我的模型資料。我添加了該.searchable()屬性,當搜索文本發生變化時,我使用模糊匹配來過濾我的物件。這會花費太多時間,并且在寫入搜索框時應用程式會滯后。所以我想異步進行搜索,這樣應用程式就不會凍結。
我嘗試使用該onChange(of:)屬性來執行此操作,然后我創建了一個Task運行異步函式的函式,因為該onChange()屬性本身不允許使用異步函式。但該應用程式仍然滯后。
這是我如何嘗試這樣做的代碼示例:
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
}
}
我真的很感激一些幫助。
uj5u.com熱心網友回復:
我認為主要問題是像前面提到的 lorem ipsum 那樣去抖動。我剛剛測驗了我的代碼,你需要在我列印的地方呼叫你的過濾器方法。
這樣您就不會過濾每個編輯文本欄位。您將在幾毫秒后進行過濾,您可以更改。
您可以在此鏈接中找到更多詳細資訊 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")
}
}
}
uj5u.com熱心網友回復:
如果你想引入去抖動,你可以添加一個Task.sleep:
.onChange(of: searchText) { newQuery in
// Cancel if still searching
searchTask?.cancel()
searchTask = Task {
try await Task.sleep(seconds: 0.5)
searchResults = await fuzzyMatch(items: modelData.items, searchText: newQuery)
}
}
如果這樣做,您將不得不更改searchTask為Task<(), Error>?.
然而,去抖可能不是全部問題。如果您的模糊過濾速度太慢,您可能需要使其異步運行并將其從主執行緒中取出:
fuzzyMatch標記為async,但當前未執行任何異步操作。讓方法簽名反映正在發生的事情可能會更容易推理一個人的代碼。為此,既然我們意識到它
fuzzyMatch是同步運行的,那么很明顯,如果它運行緩慢,它將阻塞當前執行緒。因為Task { ... }在當前 actor 上運行,你最終會阻塞主執行緒。您應該考慮使用Task.detached在后臺執行緒上獲取它。但標記searchResults為主要演員。如果取消
Task,它不會停止fuzzyMatch正在進行的。它應該檢查取消。
所以,把所有這些放在一起,也許:
struct ContentView: View {
@StateObject var modelData: ModelData()
@State var searchText = ""
@MainActor @State var searchResults: [Item] = []
@State var searchTask: Task<[Item], Error>?
let fuseSearch = Fuse()
var body: some View {
NavigationStack {
...
}
.searchable(text: $searchText)
.onChange(of: searchText) { newQuery in
Task {
searchTask?.cancel()
let task = Task.detached {
try await Task.sleep(seconds: 0.5) // debounce; if you don't want debouncing, remove this, but it can eliminate annoying updates of the UI while the user is typing
return try await fuzzyMatch(items: modelData.items, searchText: newQuery)
}
searchTask = task
searchResults = try await task.value
}
}
}
func fuzzyMatch(items: [Item], searchText: String) throws -> [Item] {
try items.filter {
try Task.checkCancellation()
return (fuseSearch.search(searchText, in: $0.name)?.score ?? 1) < 0.25
}
}
}
它不是很相關,但上面使用了這些擴展:
extension Task where Success == Never, Failure == Never {
public static func sleep(seconds: TimeInterval) async throws {
let nanoseconds = seconds * .nanosecondsPerSecond
try await sleep(nanoseconds: UInt64(nanoseconds))
}
}
extension TimeInterval {
static let nanosecondsPerSecond = Self(NSEC_PER_SEC)
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/537069.html
