SwiftUI 结合 Debounce TextField
Posted
技术标签:
【中文标题】SwiftUI 结合 Debounce TextField【英文标题】:SwiftUI Combine 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
【问题讨论】:
【参考方案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: &$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)
【讨论】:
以上是关于SwiftUI 结合 Debounce TextField的主要内容,如果未能解决你的问题,请参考以下文章
ui.bootstrap.typeahead:如何将 $http 与 debounce 结合使用
如何防止 TextField 在 SwiftUI 列表中消失?