swift 异步搜索栏

toiithl6  于 2022-11-21  发布在  Swift
关注(0)|答案(2)|浏览(130)

我需要使用搜索栏来过滤模型数据。我添加了.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
    }
}

我真的很感激你的帮助。

9rbhqvlz

9rbhqvlz1#

我认为主要的问题是debounging,就像lorem ipsum之前提到的那样。我刚刚测试了我的代码,你需要在我打印的地方调用你的filter方法。
这样你就不会过滤每一个编辑文本域。你会在几毫秒后过滤,你可以改变。
您可以在此链接中找到更多详细信息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")
        }
    }
}
mtb9vblg

mtb9vblg2#

如果要引入去抖动,只需添加一个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>?
然而,去抖动可能不是问题的全部。如果你的模糊过滤太慢,你可能需要让它异步运行,并让它离开主线程:

  1. fuzzyMatch被标记为async,但当前没有执行任何异步操作。让方法签名反映正在执行的操作可能会使推理代码变得更容易。
    1.为此,现在我们意识到fuzzyMatch是同步运行的,很明显,如果它运行得慢,它将阻塞当前线程。而且因为Task { ... }在当前参与者上运行,您最终将阻塞主线程。您应该考虑使用Task.detached在后台线程上获取它。但是将searchResults标记为在主参与者上。
    1.如果您取消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)
}

相关问题