SwiftUI:如何仅在用户停止在文本字段中输入时运行代码?

问题描述 投票:0回答:3

所以我试图制作一个搜索栏,它不会运行显示结果的代码,直到用户停止输入 2 秒(又名它应该在用户输入新字符时重置某种计时器)。我尝试使用 .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
                }
            })
        }
    }
}
swift swiftui textfield dispatch-queue
3个回答
24
投票

可能的方法是使用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)")
                }
        }
    }
}

13
投票

处理延迟搜索查询调用时通常使用两种最常用的技术:限制或去抖动。


要在 SwiftUI 中实现这些概念,您可以使用组合框架的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)
    }
}

您可以在官方文档中阅读有关组合和节流/去抖的更多信息:throttledebounce


0
投票

有一个很棒的 PropertyWrapper 解决方案,通过 CodeSlicing 可以让你简单地替换

@State var text = ""

@DebouncedState var text = ""

你就完成了。

您还可以设置去抖动量(默认为 0.3 秒,或您喜欢的任何值)。对于 1.5 秒的较长延迟:

@DebouncedState(delay: 1.5) var text = ""

这是他的视频:https://youtu.be/1uA0eKDT5-w

他的代码:https://gist.github.com/CodeSlicing/026d8481dea0e4f5f5da85ea4dce6fc4

© www.soinside.com 2019 - 2024. All rights reserved.