【问题标题】:SwiftUI Combine Debounce TextFieldSwiftUI 结合 Debounce TextField
【发布时间】:2021-02-12 00:24:52
【问题描述】:

我有一个具有 SwiftUI 应用程序生命周期的 SwiftUI 应用程序。我正在尝试设置一种标准的添加方式 键入 debounce 到 TextFields。理想情况下,我想创建自己的 TextField 修饰符 可以很容易地应用于需要编辑许多文本字段的视图。我试过一堆 方法来做到这一点,但我必须错过一些基本的东西。这是一个例子。这 不起作用:

struct ContentView: View {

    @State private var searchText = ""
    
    var body: some View {
    
        VStack {
            Text("You entered: \(searchText)")
                .padding()
            TextField("Enter Something", text: $searchText)
                .frame(height: 30)
                .padding(.leading, 5)
                .overlay(
                    RoundedRectangle(cornerRadius: 6)
                        .stroke(Color.blue, lineWidth: 1)
                )
                .padding(.horizontal, 20)
                .onChange(of: searchText, perform: { _ in
                    var subscriptions = Set<AnyCancellable>()
                
                    let pub = PassthroughSubject<String, Never>()
                    pub
                        .debounce(for: .seconds(1), scheduler: DispatchQueue.main)
                        .collect()
                        .sink(receiveValue: { t in
                            self.searchText = t.first ?? "nothing"
                        } )
                        .store(in: &subscriptions)
                })
        }
    }
}

任何指导将不胜感激。 Xcode 12.4、iOS 14.4

【问题讨论】:

    标签: ios xcode swiftui combine


    【解决方案1】:

    我认为您必须保留两个变量:一个用于用户键入时字段中的文本,另一个用于去抖动文本。否则,用户将看不到实时输入的输入,我假设这不是您想要的行为。我猜这可能是针对更标准的用例,例如,一旦用户暂停输入后执行数据提取。

    我喜欢 ObservableObjects 和 Combine 来管理这类事情:

    class TextFieldObserver : ObservableObject {
        @Published var debouncedText = ""
        @Published var searchText = ""
        
        private var subscriptions = Set<AnyCancellable>()
        
        init() {
            $searchText
                .debounce(for: .seconds(1), scheduler: DispatchQueue.main)
                .sink(receiveValue: { [weak self] t in
                    self?.debouncedText = t
                } )
                .store(in: &subscriptions)
        }
    }
    
    struct ContentView: View {
        @StateObject var textObserver = TextFieldObserver()
        
        @State var customText = ""
        
        var body: some View {
        
            VStack {
                Text("You entered: \(textObserver.debouncedText)")
                    .padding()
                TextField("Enter Something", text: $textObserver.searchText)
                    .frame(height: 30)
                    .padding(.leading, 5)
                    .overlay(
                        RoundedRectangle(cornerRadius: 6)
                            .stroke(Color.blue, lineWidth: 1)
                    )
                    .padding(.horizontal, 20)
                Divider()
                Text(customText)
                TextFieldWithDebounce(debouncedText: $customText)
            }
        }
    }
    
    struct TextFieldWithDebounce : View {
        @Binding var debouncedText : String
        @StateObject private var textObserver = TextFieldObserver()
        
        var body: some View {
        
            VStack {
                TextField("Enter Something", text: $textObserver.searchText)
                    .frame(height: 30)
                    .padding(.leading, 5)
                    .overlay(
                        RoundedRectangle(cornerRadius: 6)
                            .stroke(Color.blue, lineWidth: 1)
                    )
                    .padding(.horizontal, 20)
            }.onReceive(textObserver.$debouncedText) { (val) in
                debouncedText = val
            }
        }
    }
    

    我举了两个例子——顶部,容器视图 (ContentView) 拥有 ObservableObject,底部,它被制成一个更可重用的组件。

    【讨论】:

    • 非常优雅的设计???
    • @jnpdx 好奇是否会在每次 searchText 更改时重新计算视图,从而导致不必要的渲染? TextFieldObserver 不会被文本字段用户输入更新,因为它是一个已发布的属性,视图不会被重新渲染吗?
    • 我更多地考虑使用一个 CurrentValueSubject 作为 textField 的文本,这样视图就不会更新,然后使用另一个 @Published 属性。
    • TextFieldObserver中sink订阅中的强引用导致内存泄漏,改成.sink(receiveValue: { [weak self] in self?.debouncedText = $0 } )
    • 当搜索为空但我有 1 个场景时,这非常有效,请阅读以下场景。我有 1 个表单,我在那里添加了去抖动,但我想让它在该表单处于编辑模式时工作。目前,我只是在表单中预填数据,它会命中 API。提前致谢
    【解决方案2】:

    来自@jnpdx 的文本去抖动器的一点简化版

    请注意,.assign(to: &amp;$debouncedText) 不会创建引用循环并会自动为您管理订阅

    class TextFieldObserver : ObservableObject {
        
        @Published var debouncedText = ""
        @Published var searchText = ""
            
        init(delay: DispatchQueue.SchedulerTimeType.Stride) {
            $searchText
                .debounce(for: delay, scheduler: DispatchQueue.main)
                .assign(to: &$debouncedText)
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2019-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-20
      • 2020-02-01
      • 1970-01-01
      • 2023-01-10
      相关资源
      最近更新 更多