SwiftUI: 如何在用户停止在TextField中输入时运行代码?

9

所以我正在尝试制作一个搜索栏,直到用户停止输入两秒钟后才运行显示结果的代码(也就是说,当用户输入新字符时,应该重置某种计时器)。我尝试使用.onChange()和AsyncAfter DispatchQueue,但它并不起作用(我认为我理解为什么当前实现方式不起作用,但我不确定是否正确地解决了这个问题)...

struct SearchBarView: View {
    @State var text: String = ""
    @State var justUpdatedSuggestions: Bool = false
    var body: some View {
        ZStack {
            TextField("Search", text: self.$text).onChange(of: self.text, perform: { newText in
                appState.justUpdatedSuggestions = true
                DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
                    appState.justUpdatedSuggestions = false
                })
                if justUpdatedSuggestions == false {
                    //update suggestions
                }
            })
        }
    }
}
2个回答

23

可能的方法是使用Combine框架中的debounce。为了使用它,最好创建一个单独的视图模型,并为搜索文本创建已发布的属性。

这里有一个演示。准备并在Xcode 12.4 / iOS 14.4上进行测试。

import Combine

class SearchBarViewModel: ObservableObject {
    @Published var text: String = ""
}

struct SearchBarView: View {
    @StateObject private var vm = SearchBarViewModel()
    var body: some View {
        ZStack {
            TextField("Search", text: $vm.text)
                .onReceive(
                    vm.$text
                        .debounce(for: .seconds(2), scheduler: DispatchQueue.main)
                ) {
                    guard !$0.isEmpty else { return }
                    print(">> searching for: \($0)")
                }
        }
    }
}

1
为什么我们不只是使用一个简单的计时器来解决这个问题呢?这样应该可以解决问题,而无需使用Combine。 - ios coder

13

在处理延迟搜索查询调用时,通常使用两种最常见的技术:节流或去抖动。


要在SwiftUI中实现这些概念,可以使用Combine框架的throttle/debounce方法。

例如,实现可能如下所示:

import SwiftUI
import Combine

final class ViewModel: ObservableObject {
    private var disposeBag = Set<AnyCancellable>()

    @Published var text: String = ""

    init() {
        self.debounceTextChanges()
    }

    private func debounceTextChanges() {
        $text
            // 2 second debounce
            .debounce(for: 2, scheduler: RunLoop.main)

            // Called after 2 seconds when text stops updating (stoped typing)
            .sink {
                print("new text value: \($0)")
            }
            .store(in: &disposeBag)
    }
}

struct ContentView: View {
    @StateObject var viewModel = ViewModel()

    var body: some View {
        TextField("Search", text: $viewModel.text)
    }
}
您可以在官方文档中了解有关Combine和throttle / debounce的更多信息:throttledebounce

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接