use*_*617 4 xcode ios swiftui combine
I have a SwiftUI app with SwiftUI App life cycle. I'm trying to setup a standard way to add typing debounce to TextFields. Ideally, I'd like to create my own TextField modifier that can easily be applied to views that have many textfields to edit. I've tried a bunch of ways to do this but I must be missing something fundamental. Here's one example. This does not work:
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)
})
}
}
}
Run Code Online (Sandbox Code Playgroud)
Any guidance would be appreciated. Xcode 12.4, iOS 14.4
Iva*_*kiy 19
来自 @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)
}
}
Run Code Online (Sandbox Code Playgroud)
Alb*_*ori 11
如果您无法使用ObservableObject(即,如果您的视图由状态机驱动,或者您将输入结果传递给委托,或者只是发布输入),则有一种方法可以仅使用来完成去抖查看代码。这是通过将文本更改转发到本地Publisher,然后消除该输出的抖动来完成的Publisher。
struct SomeView: View {
@State var searchText: String = ""
let searchTextPublisher = PassthroughSubject<String, Never>()
var body: some View {
TextField("Search", text: $searchText)
.onChange(of: searchText) { searchText in
searchTextPublisher.send(searchText)
}
.onReceive(
searchTextPublisher
.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
) { debouncedSearchText in
print(debouncedSearchText)
}
}
}
Run Code Online (Sandbox Code Playgroud)
或者如果广播更改:
struct DebouncedSearchField: View {
@Binding var debouncedSearchText: String
@State private var searchText: String = ""
private let searchTextPublisher = PassthroughSubject<String, Never>()
var body: some View {
TextField("Search", text: $searchText)
.onChange(of: searchText) { searchText in
searchTextPublisher.send(searchText)
}
.onReceive(
searchTextPublisher
.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
) { debouncedSearchText in
self.debouncedSearchText = debouncedSearchText
}
}
}
Run Code Online (Sandbox Code Playgroud)
但是,如果您有选择,采用该ObservableObject方法可能会更“正确”。
I think you'll have to keep two variables: one for the text in the field as the user is typing and one for the debounced text. Otherwise, the user wouldn't see the typing coming in in real-time, which I'm assuming isn't the behavior you want. I'm guessing this is probably for the more standard use case of, say, performing a data fetch once the user has paused their typing.
I like ObservableObjects and Combine to manage this sort of thing:
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: { 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
}
}
}
Run Code Online (Sandbox Code Playgroud)
I included two examples -- the top, where the container view (ContentView) owns the ObservableObject and the bottom, where it's made into a more-reusable component.
| 归档时间: |
|
| 查看次数: |
1412 次 |
| 最近记录: |