How to clear all text in a UIViewRepresentable Text Field with a view modifier

428 Views Asked by At

I'm trying to delete all text inside a Text Field when a button to the right of the text field is tapped. When I tap the clear button it behaves as though the text has been cleared. It shows the placeholder text which is set to be shown when the Text Field is cleared. However, the text is still showing 'underneath' the placeholder text. I can delete the typed in text by using the delete key on the keyboard, one character at a time, but clearing all at once should be a quicker option.

I have a UIViewRepresentable for a TextField as below. The reason I'm using a UIViewRepresentable is because I need the keyboard language layout to change languages as required by the user. SwiftUI doesn't currently support this:

struct UITextFieldViewRepresentable: UIViewRepresentable {
    @Binding var language: String
    @Binding var text: String
    var onCommit: (() -> Bool)
    
    init(language: Binding<String>, text: Binding<String>, onCommit: @escaping() -> Bool = { return true }) {
        self._language = language
        self._text = text
        self.onCommit = onCommit
    }
    
    func makeUIView(context: Context) -> WordTextField {
        let textField = WordTextField(onCommit: onCommit)
        textField.language = self.language
        textField.textAlignment = .center
        textField.font = UIFont.systemFont(ofSize: 15, weight: .regular)
        return textField
    }
    
    func updateUIView(_ uiView: WordTextField, context: Context) {
        uiView.textDidChange = {
            text = $0
        }
        
            // Change the keyboard language only when uiView.language != self.language
            //
        if uiView.language != self.language {
            uiView.language = self.language
        }
    }
}

class WordTextField: UITextField, UITextFieldDelegate {
    var textDidChange: ((String) -> Void)?
    var onCommit: (() -> Bool)
    
    var language: String? {
        didSet {
            if self.isFirstResponder{
                self.resignFirstResponder()
                self.becomeFirstResponder()
            }
        }
    }
    
    init(textDidChange: ( (String) -> Void)? = nil, onCommit: @escaping () -> Bool = { return true }, language: String? = nil) {
        self.textDidChange = textDidChange
        self.onCommit = onCommit
        self.language = language
        super.init(frame: .zero)
        self.delegate = self
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func textFieldDidChangeSelection(_ textField: UITextField) {
        textDidChange?(textField.text ?? "")
    }
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        return onCommit()
    }
    
    override var textInputMode: UITextInputMode? {
        if let language = self.language {
            print("text input mode: \(language)")
            for inputMode in UITextInputMode.activeInputModes {
                if let inputModeLanguage = inputMode.primaryLanguage, inputModeLanguage == language {
                    return inputMode
                }
            }
        }
        return super.textInputMode
    }
}

I call it as below:

@State private var userAnswer: String = ""
@State private var keyboardLanguage: String = ""
@State private var hintColour: Color = .green
@State private var hintDisabled: Bool = false
UITextFieldViewRepresentable(language: $keyboardLanguage, text: Binding<String>(
    get: { self.userAnswer },
    set: {
           self.userAnswer = $0.allowedCharacters(string: $0)
           self.enableHint()
    }), onCommit: {
           checkAnswer()
           return true
})
    .showClearButton(userAnswer: $userAnswer, hintDisabled: $hintDisabled, hintColour: $hintColour)

The view modifier .showClearButton does not clear the userAnswer text when tapped. Tapping the keyboard delete button clears userAnswer one character at a time.

The view modifier code is below:

extension View {
    func showClearButton(userAnswer: Binding<String>, hintDisabled: Binding<Bool>, hintColour: Binding<Color>) -> some View {
        self.modifier(TextFieldClearButton(text: userAnswer, hintDisabled: hintDisabled, hintColour: hintColour))
    }
}
struct TextFieldClearButton: ViewModifier {
    
    @Binding var text: String
    @Binding var hintDisabled: Bool
    @Binding var hintColour: Color
    
    func body(content: Content) -> some View {
        HStack {
            content
            if !text.isEmpty {
                Button(
                    action: { self.text = ""; hintDisabled = false; hintColour = Color.systemBlue },
                    label: {
                        Image(systemName: "delete.left")
                            .foregroundColor(Color(UIColor.gray))
                            .imageScale(.large)
                            .frame(width: 44, height: 44, alignment: .trailing)
                    }
                )
            }
        }
    }
}
1

There are 1 best solutions below

0
malhal On

You forgot to set the new text on the UITextField which in the case of the clear button is an empty string, fix like this:

    func updateUIView(_ uiView: WordTextField, context: Context) {

        // set the new text
        uiView.text = text

        // you also forgot to set the new onCommit
        uiView.onCommit = onCommit


        uiView.textDidChange = {
            text = $0
        }
        
            // Change the keyboard language only when uiView.language != self.language
            //
        if uiView.language != self.language {
            uiView.language = self.language
        }
    }

Also, you could benefit from a Coordinator here, it works like this:

    func makeCoordinator() -> Coordinator {
        Coordinator()
    }

    func makeUIView(context: Context) -> UITextField {
        context.coordinator.textField
    }

    func updateUIView(_ uiView: UITextField, context: Context) {
        context.coordinator.textDidChange = {
            text = $0
        }
        uiView.onCommit = onCommit

        uiView.text = text // maybe could check it is different first
        if uiView.language != self.language {
            uiView.language = self.language
        }
    }


class Coordinator: NSObject, UITextFieldDelegate {

    lazy var textField: {
        let textField = UITextField()
        textField.textAlignment = .center
        textField.font = UIFont.systemFont(ofSize: 15, weight: .regular)
        return textField
    }()

    // your closure properties
    // all your delegate methods
}