Search code examples
keyboardactionswiftui

Action when user click on the delete button on the keyboard in SwiftUI


I try to run a function when the user click on the delete button on the keyboard when he try to modify a Textfield.

How can I do that ?

enter image description here


Solution

  • Yes it is possible, however it requires subclassing UITextField and creating your own UIViewRepresentable

    This answer is based on the fantastic work done by Costantino Pistagna in his medium article but we need to do a little more work.

    Firstly we need to create our subclass of UITextField, this should also conform to the UITextFieldDelegate protocol.

    class WrappableTextField: UITextField, UITextFieldDelegate {
        var textFieldChangedHandler: ((String)->Void)?
        var onCommitHandler: (()->Void)?
        var deleteHandler: (() -> Void)?
    
        override func deleteBackward() {
            super.deleteBackward()
            deleteHandler?()
        }
    
        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            if let nextField = textField.superview?.superview?.viewWithTag(textField.tag + 1) as? UITextField {
                nextField.becomeFirstResponder()
            } else {
                textField.resignFirstResponder()
            }
            return false
        }
    
        func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
            if let currentValue = textField.text as NSString? {
                let proposedValue = currentValue.replacingCharacters(in: range, with: string)
                textFieldChangedHandler?(proposedValue as String)
            }
            return true
        }
    
        func textFieldDidEndEditing(_ textField: UITextField) {
            onCommitHandler?()
        }
    }
    

    Because we are creating our own implementation of a TextField we need three functions that we can use for callbacks.

    1. textFieldChangeHandler this will be called when the text property updates and allows us to change the state value associated with our Textfield.
    2. onCommitHandler this will be called when we have finished editing our TextField
    3. deleteHandler this will be called when we perform he delete action.

    The code above shows how these are used. The part that you are particularly interested in is the override func deleteBackward(), by overriding this we are able to hook into when the delete button is pressed and perform an action on it. Depending on your use case, you may want the deleteHandler to be called before you call the super.

    Next we need to create our UIViewRepresentable.

    struct MyTextField: UIViewRepresentable {
        private let tmpView = WrappableTextField()
    
        //var exposed to SwiftUI object init
        var tag:Int = 0
        var placeholder:String?
        var changeHandler:((String)->Void)?
        var onCommitHandler:(()->Void)?
        var deleteHandler: (()->Void)?
    
        func makeUIView(context: UIViewRepresentableContext<MyTextField>) -> WrappableTextField {
            tmpView.tag = tag
            tmpView.delegate = tmpView
            tmpView.placeholder = placeholder
            tmpView.onCommitHandler = onCommitHandler
            tmpView.textFieldChangedHandler = changeHandler
            tmpView.deleteHandler = deleteHandler
            return tmpView
        }
    
        func updateUIView(_ uiView: WrappableTextField, context: UIViewRepresentableContext<MyTextField>) {
            uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
            uiView.setContentHuggingPriority(.defaultLow, for: .horizontal)
        }
    }
    

    This is where we create our SwiftUI version of our WrappableTextField. We create our WrappableTextField and its properties. In the makeUIView function we assign these properties. Finally in the updateUIView we set the content hugging properties, but you may choose not to do that, it really depends on your use case.

    Finally we can create a small working example.

    struct ContentView: View {
    
        @State var text = ""
    
        var body: some View {
            MyTextField(tag: 0, placeholder: "Enter your name here", changeHandler: { text in
                // update the state's value of text
                self.text = text
            }, onCommitHandler: {
                // do something when the editing finishes
                print("Editing ended")
            }, deleteHandler: {
                // do something here when you press delete
                print("Delete pressed")
            })
        }
    }