I have a text field for searching items in an array, and I am using a model, that has a published text property, bound to the field to call the search function every time the text changes.
My current issue is that every time anything happens with the text field, it sends a notification that is has changed, therefore the first time it appears it sends one, every time it is focused, defocused, or a letter was typed it sends two.
This shows how many times the field sends changes:
This is a simplified example of the code:
struct ContentView: View {
@ObservedObject var model = Model()
@FocusState var focused: Bool
var body: some View {
VStack {
TextField("Field", text: $model.text)
.submitLabel(.search)
.focused($focused)
Text("Field has sent changes \(model.textChanged) times")
Button(action: { focused = false }) {
Text("Defocus")
}
}
.padding()
}
}
class Model: ObservableObject {
@Published var text: String = ""
@Published var textChanged: Int = 0
private var cancellables = Set<AnyCancellable>()
init() {
$text
.sink { text in
self.textChanged += 1 // This is where i am doing my search call in the actual code
}
.store(in: &cancellables)
}
}
I have tried using .onReceive
on the TextField
, and .onChange($text)
on the view instead of .sink
, but that didn't change anything. I know that this is the default behaviour of TextField
, but I would like to only execute the search, when the text has changed and not every time something happens with the field. Putting the search call in a didSet
on the text
property is also not an option.
You could just attach .onChange
to the TextField
(or you can attach it to any part of the body
in fact).
You said you tried this before, but you used a binding. It shouldn't be a binding:
@State private var counter = 0
var body: some View {
VStack {
TextField("Field", text: $model.text)
.submitLabel(.search)
.focused($focused)
// pre iOS 17: .onChange(of: model.text) { newVal in
.onChange(of: model.text) { oldVal, newVal in
counter += 1
}
Text("Field has sent changes \(model.textChanged) times, counter=\(counter)")
Button(action: { focused = false }) {
Text("Defocus")
}
}
.padding()
}