Swiftui: How to Only Run Code When the User Stops Typing in a Textfield

SwiftUI: How to only run code when the user stops typing in a TextField?

The possible approach is to use debounce from Combine framework. To use that it is better to create separated view model with published property for search text.

Here is a demo. Prepared & tested with Xcode 12.4 / iOS 14.4.

import Combine

class SearchBarViewModel: ObservableObject {
@Published var text: String = ""
}

struct SearchBarView: View {
@StateObject private var vm = SearchBarViewModel()
var body: some View {
ZStack {
TextField("Search", text: $vm.text)
.onReceive(
vm.$text
.debounce(for: .seconds(2), scheduler: DispatchQueue.main)
) {
guard !$0.isEmpty else { return }
print(">> searching for: \($0)")
}
}
}
}

How to create a function when the user stops typing in swift

I would use something like:

func textFieldDidChangeSelection(_ textField: UITextField) {

checkmarkImageView.isHidden = True

let timer = Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: true)
}

And:

@objc func fireTimer() {

checkmarkImageView.isHidden = False
}

Instead of creating the imageView each time, set it up as a IBOutlet that's initially hidden.

Detect when user stopped / paused typing in Swift

UPDATE 2016/09/01:

We can use NSTimers or (since swift 2.0) NSObject's performSelector and friends.

Aproach 1 : performSelector

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
NSObject.cancelPreviousPerformRequests(
withTarget: self,
selector: #selector(ViewController.getHintsFromTextField),
object: textField)
self.perform(
#selector(ViewController.getHintsFromTextField),
with: textField,
afterDelay: 0.5)
return true
}

func getHintsFromTextField(textField: UITextField) {
print("Hints for textField: \(textField)")
}

Approach 2: NSTimer

var timer: NSTimer? = nil

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
timer?.invalidate()
timer = Timer.scheduledTimer(
timeInterval: 0.5,
target: self,
selector: #selector(ViewController.getHints),
userInfo: ["textField": textField],
repeats: false)
return true
}

func getHints(timer: Timer) {
var userInfo = timer.userInfo as! [String: UITextField]
print("Hints for textField: \(userInfo["textField"])")
}

Note I am passing the textField to delayed functions. It is not always required but it could make your life easier when textField is not easy to access or when dealing with various text fields.

How NSTimer approach is different from performSelector?

When you call performSelector the target is retained (in swift the target is always self) but when you use NSTimer the target is NOT retained. This means, if you use NSTimers, you have to make sure target (in this case self) is alive by the time the timer fires. Otherwise the a crash will occur.

(BTW: performSelector uses NSTimer internally )

If you are interested in GCD timers this gist a good place start:
maicki/TimerWithGCD.md

App freeze when tap on textfield in conditional section

So with Xcode 13.2.1 on Intel and iOS 15.2 I can only get this example code to lock-up in the simulator and on a real device if I type a number of more than three digits in the 'Amount EUR' and then attempt to 'Set the Max to 999' i.e. a three digit number.

In previous versions of SwiftUI the only input binding TextField would work with was String. It seems likely that although the current version accepts binding to numerics the way it is doing this is to map, and the underlying mechanism remains that of the original String based TextField (typing in the TextField with an attached keyboard still unfortunately renders non-numeric characters regardless of keyboardType as it did the last time I experimented https://gist.github.com/shufflingB/23daafa5253c3355cdf18934599cd54c)

For whatever reason this new(ish) mapping process appears to be getting confused when more than three digits are entered and the TextField is then by default automatically adding a number grouping character.

Two work-arounds seem to address the problem:

  1. Push the change to amount triggered by Set the Max ... to a later state update cycle using DispatchQueue.main.asyncAfter

  2. Give an option to remove the troublesome grouping character from the TextField.

struct ContentView: View {
@Environment(\.presentationMode) var presentationMode
@State private var displaySection = true
@State private var amount: Double?
@FocusState private var isFocused: Bool

var body: some View {
NavigationView {
Form {
if displaySection {
Section {
VStack {
HStack {
Text("Amount EUR")
Spacer()
TextField("Type amount", value: $amount, format: .number)
.keyboardType(.numberPad)
.multilineTextAlignment(.trailing)
.focused($isFocused)

/* Alternative to Dispatch, remove number grouping
TextField("Type amount", value: $amount, format: .number.grouping(.never))
.keyboardType(.numberPad)
.multilineTextAlignment(.trailing)
.focused($isFocused)
*/
}
Text("Set MAX (999)")
.frame(maxWidth: .infinity, alignment: .leading)
.onTapGesture {
isFocused = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { // <- Push change to subsequent update cycle
amount = 999
}
}
}
}
}
}
}
}
}

How to detect when a TextField loses the focus in SwiftUI for iOS?

You can use the initializer init(_:text:onEditingChanged:onCommit:) present in textfield. There you will be getting an action triggered when you begin editing and end editing. You can find out the minimal example below.

import SwiftUI

struct ContentView: View {
@State private var greeting: String = "Hello world!"
var body: some View {
TextField("Welcome", text: $greeting, onEditingChanged: { (editingChanged) in
if editingChanged {
print("TextField focused")
} else {
print("TextField focus removed")
}
})
}
}

Hope this helps.

Editing a TextField in preview

Since the comment was a legit answer I just copy paste it here. Glad it worked for you as well

There has been an Xcode bug in the past were live preview TextField input was broken. Seems to be back :(. I just experienced the same on 13.2.1. running the preview on iOS 15.2. switching to an iOS 14.3 simulator seems to fix it in my case. (wait for the Preparing iPhone Simulator for Previews to finish). Related to

How to detect live changes on TextField in SwiftUI?

You can create a binding with a custom closure, like this:

struct ContentView: View {
@State var location: String = ""

var body: some View {
let binding = Binding<String>(get: {
self.location
}, set: {
self.location = $0
// do whatever you want here
})

return VStack {
Text("Current location: \(location)")
TextField("Search Location", text: binding)
}

}
}


Related Topics



Leave a reply



Submit