10
Jan
2023
Detect Backspace in SwiftUI
Reading time: 2 min
This recipe shows how to detect backspace event in SwiftUI TextField. The method can figure out both if it's a forward or in-text backspace, and doesn't rely on hacks on hidden characters.
The end result looks like this:

There are numerous solutions out there that try to figure out if backspace was pressed by introducing hidden characters (such as zero-width space), but those are hacky and don't work well as they:
- Mess up the cursor and selection,
- Mess up autocompletion,
- Hide the placeholder (since the textfield technically isn't empty anymore), and
- Force additional input processing to eliminate the extra hidden character(s).
The solution in this recipe creates a custom UITextField subclass and overrides its deleteBackward method. Then, it wraps this custom view and exposes it to SwiftUI using UIViewRepresentable.
Here's the code for the custom UITextField and its SwiftUI wrapper:
struct EnhancedTextField: UIViewRepresentable {
let placeholder: String // text field placeholder
@Binding var text: String // input binding
let onBackspace: (Bool) -> Void // true if backspace on empty input
func makeCoordinator() -> EnhancedTextFieldCoordinator {
EnhancedTextFieldCoordinator(textBinding: $text)
}
func makeUIView(context: Context) -> EnhancedUITextField {
let view = EnhancedUITextField()
view.placeholder = placeholder
view.delegate = context.coordinator
return view
}
func updateUIView(_ uiView: EnhancedUITextField, context: Context) {
uiView.text = text
uiView.onBackspace = onBackspace
}
// custom UITextField subclass that detects backspace events
class EnhancedUITextField: UITextField {
var onBackspace: ((Bool) -> Void)?
override init(frame: CGRect) {
onBackspace = nil
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError()
}
override func deleteBackward() {
onBackspace?(text?.isEmpty == true)
super.deleteBackward()
}
}
}
// the coordinator is here to allow for mapping text to the
// binding using the delegate methods
class EnhancedTextFieldCoordinator: NSObject {
let textBinding: Binding<String>
init(textBinding: Binding<String>) {
self.textBinding = textBinding
}
}
extension EnhancedTextFieldCoordinator: UITextFieldDelegate {
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
textBinding.wrappedValue = textField.text ?? ""
return true
}
}
Then, you can put it to use like this:
struct BackspaceEventTest: View {
@State private var inputText = ""
var body: some View {
EnhancedTextField(placeholder: "Input text", text: $inputText) { onEmpty in
print("Backspace pressed, onEmpty? \(onEmpty) at \(Date().ISO8601Format())")
}
}
}