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.
2
Answers
You could just attach
.onChange
to theTextField
(or you can attach it to any part of thebody
in fact).You said you tried this before, but you used a binding. It shouldn’t be a binding:
If you want to keep your model structure, then with minimal change to your code I’d suggest to use
removeDuplicates()
. The updated code to your Model will be:Please note that the first change you get in this
sink
is because you are assigning an empty string to thetext
in your model.