Search code examples
swiftuitextfieldresignfirstresponder

Swift: Can't get UITextField to dismiss keyboard


I have two textFields and a Done button in my VC but I'm having some problems with the ending of editing.

My textFieldDidEndEditing method is called when I tap on one textField after being inside the other one, or when I tap outside the textField (because I added a tap recognizer to the parent view) but not when I tap the Done button.

And, most important (especially when I run on an actual device), the keyboard won't disappear under any of these circumstances, even though my textFieldDidEndEditing method calls resignFirstResponder().

Why isn't the keyboard dismissing? Also, is there a way to have textFieldDidEndEditing get called when I tap outside the field just automatically (without having it come from the tap recognizer)? It just seems like this should be how it works, but if I'm wrong, I'm wrong.

Here's some pertinent parts of my code.

1.Trying to dismiss the keyboard. The first part of this method works, and the value is stored (when the method is called at all, that is). At no point does the cursor disappear from the textField, nor does the keyboard get dismissed.

        func textFieldDidEndEditing(_ textField: UITextField) {
    if let playerName = textField.text, let playerNum = nameFields.index(of: textField) {
        playerNames[playerNum] = playerName
    }
    resignFirstResponder()
}

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    textFieldDidEndEditing(textField)
    return true
}

Also, here's a curious thing: when I set a breakpoint in textFieldDidEndEditing and debug, enter a value in one field and hit Done, it segues to the next scene, and then stops at textFieldDidEndEditing, which at this point has no effect (the values may be stored but they aren't reflected in the new scene).

2.Trying to add the tap recognizer to the done button. I don't have an outlet to the done button in my code, just out of laziness, so that's probably the best solution. But, I'm still interested in why this doesn't work. This is identical to the code that defines the tap recognizer that's working in the parent view.

func dismiss(_ sender:UITapGestureRecognizer) {
    nameFields.forEach { textFieldDidEndEditing($0) }
}

override func viewDidAppear(_ animated: Bool) {
    for view in view.subviews where view is UIButton {
        let dismissTextField = UITapGestureRecognizer(target: self, action: #selector(dismiss(_:)))
        dismissTextField.numberOfTapsRequired = 1
        view.addGestureRecognizer(dismissTextField)
    }
}

Solution

  • You need to call resignFirstResponder inside textFieldShouldReturn method instead of calling textFieldDidEndEditing.

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
    

    Also in your TapGesture method simply call endEditing(_:) with your view instead of looping through the array of textFields.

    func dismiss(_ sender:UITapGestureRecognizer) {
        self.view.endEditing(true)
    }