3

I'm a noob here and in iOS world. I am having trouble dismiss keyboard on a specific case in my very simple todo list iOS app.

I'd like the keyboard to get dismiss when user taps anywhere outside the current text field or the keyboard itself. So far, I got the keyboard dismisses just fine (thanks to you guys here in stack overflow) when user taps on the UITableView, or most element on my app. HOWEVER, when user taps on another UITextField, the keyboard does not go away.

FYI, here's the list of existing threads I researched so far but have yet to solve this issue. 1) How to dismiss keyboard iOS programmatically 2) Resigning First Responder for multiple UITextFields 3) Dismissing the First Responder/Keyboard with multiple Textfields 4) (a few more at least but I lost track :( )

Here's what I did so far:

(in viewDidLoad())
// Add 'tap' gesture to dismiss keyboard when done adding/editing to-do item
var tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "tapOutside:")
tap.cancelsTouchesInView = true
self.view.addGestureRecognizer(tap)

func tapOutside(tapOutside: UIGestureRecognizer) {
   // Dismiss keyboard
   self.view.endEditing(true)
}

@IBAction func EditingDidBegin(sender: UITextField) {
   // Highlight the text field which user is editing
   self.highlightTextField(sender, highlight: true)
}

@IBAction func EditingDidEnd(sender: UITextField) {
   // Undo text field highlight
   self.highlightTextField(sender, highlight: false)

   self.view.endEditing(true)  // try this option and not working
   self.setEditing(false, animated: true)   // try this option and not working
   sender.resignFirstResponder()   // try this option and not working
   UIApplication.sharedApplication().becomeFirstResponder()   // try this option and not working

   ... // below is my code to update the todo item
}

I also tried to print out all subviews.isFirstResponder() of my view. All of it return false. I also tried override touchesBegan of my UIViewController, and inside it just calls self.view.endEditing(true) and call its super's. This also does not work.

Please help. :(

TIA!

UPDATE: You guys are awesome! :D I got it working now thanks to you guys. There were several mistakes / messed up as I'm learning new framework. So here's what I did.

1) I did not set UITextField delegate correctly. Mistake: I ctrl-draged textfield in xcode and link my viewController as delegate and thought that should work out. I will still need to research and understand better why. Solution: I removed that ctrl-drag link and explicitly call myTextField.delegate = self in tableView:cellForRowAtIndexPath. And that did it. Thanks @Sidewalker

2) Mistake: I have a mixed of textFieldShouldBeginEditing, etc. and @IBAction func EditingDidBegin. So I got myself into the situation where textFieldShouldBeginEditing got the call, but EditingDidBegin did not get call. Solution: Once I set the delegate = self explicitly and stick with implementing textField... methods and not use any @IBAction for textField, things just work.

Community
  • 1
  • 1
wotzup
  • 33
  • 4

5 Answers5

0

Well the keyboard isn't going away because it doesn't expect to have to. The new UITextField is just becoming the first responder while the other resigns. If you don't want a textField to become the first responder if another is already, you're going to have to cut it off before it gets the chance to. I would try to implement textFieldShouldBeginEditing and figuring out the logic there.

I'm not in love with the way this looks but this should do something along those lines.

func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
    for subView in self.view.subviews{
        if(subView.isKindOfClass(UITextField)){
            if(subView.isFirstResponder()){
                subView.resignFirstResponder();
                return false;
            }
        }
    }
    return true;
}
Dare
  • 2,379
  • 1
  • 10
  • 20
  • Hi user3117251, Thanks a lot for your reply and sample solution. I tried and it still didn't work :( However, it gave me some good idea to try implement textFieldShouldBeginEditing to figure things out further though. So thanks! I tried your code, and that if condition (subView.isFirstResponder() is never satisfied. – wotzup Apr 03 '15 at 20:39
  • An interesting find out to me is: (context: todo app has 3 sample todo item A, B, C) 1) When I first started the app and tap on item A - UITextField. I got calls to textFieldShouldBeginEditing AND textFieldDidBeginEditing. **HOWEVER**, after that I tap on item B, I only got calls to textFieldShouldBeginEditing of item B, textFieldDidEditingEnd of item A (in this exact order) but **never** got a call to textFieldDidEditingBegin of item B – wotzup Apr 03 '15 at 20:40
  • Ah, that makes a lot of sense if didEndEditing gets called first. Try removing sender.resignFirstResponder() from your didEndEditing method. So long as that gets called first you're right that the condition will never be true. I have it up and working in a sample app I made so unless something else is going on it should hypothetically work. – Dare Apr 03 '15 at 20:44
  • The other trick – make sure you conform the UITextFieldDelegate like others have mentioned and that all your textFields set their delegate to self. If you don't do that the delegate methods in your class will never be called. Check out Sidetalker's answer if the delegate situation is new to you. – Dare Apr 03 '15 at 20:51
0

Here's one option... We're going to add a boolean flag to determine whether or not we're in a textField when an edit attempt for another textField begins

Make your class adhere to UITextFieldDelegate

class MyClass: UIViewController, UITextFieldDelegate

Don't forget to set the delegate, we'll add the flag as well

myTextField.delegate = self
var inField = false

Implement "textFieldShouldBeginEditing" and "textFieldDidBeginEditing"

func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
    if inField {
        inField = false
        return false
    } 
    return true
}

func textFieldDidBeginEditing(textField: UITextField) {
    inField = true
}

I prefer tracking things like this rather than identifying subviews as it allows the flag to be utilized elsewhere and cuts down code complexity.

Nebojsa Nadj
  • 572
  • 2
  • 12
Sidetalker
  • 598
  • 2
  • 14
  • Hi Sidetalker, Thanks a lot for your reply. Quick side questions: (but related to this) 1) I did not originally set the delegate, I only implement the UITextFieldDelegate protocol. My methods such as textFieldDidBeginEditing got called without setting the delegate as you explicitly show me in your sample (myTextField.delegate = self). Is this the same as using Xcode UI to hook up by ctrl-drag and select my UIViewController as delegate for the UITextField in each of my UITableViewCell? – wotzup Apr 03 '15 at 21:02
  • 2) another side bar question: I now set myTextField.delegate = self in my tableView:cellForRowAtIndexPath. I do not see the different in the app behavior. 3) I am very confused why my textFieldShouldBeginEditing get called for item B, but my textFieldDidBeginEditing does **not** get called. (context example: my todo app has current 3 items: A, B, C. I tap on item A and get calls for textFieldShouldBeginEditing and textFieldDidBeginEditing for item A. then I tap item B, I only get call for textFieldSholdBeginEditing, but not textFieldDidBeginEditing for item A.Could you help me understand? TIA – wotzup Apr 03 '15 at 21:05
  • 1) Yep, those do the same thing – Sidetalker Apr 03 '15 at 21:43
  • 2) I'd need to look at the code to make a good guess on that - feel free to pastebin it – Sidetalker Apr 03 '15 at 21:44
  • 3) If textFieldShouldBeginEditing returns FALSE then the edit request is cancelled and textFieldDidBeginEditing is never called. The default function always returns true but we've changed it so that it will conditionally return false when moving from one textfield to another - does that answer your question? – Sidetalker Apr 03 '15 at 21:45
0

First set all the UITextField (your are creating) delegate as self and create one UITextField member variable. Now implement "textFieldDidBeginEditing" delegate method and assign the textfield to your member UITextField variable. As given below

func textFieldDidBeginEditing(textField: UITextField) {
    yourMemberVariable = textField;
}

So now whenever you want to dismiss the keyboard call the dismiss method on "yourMemberVariable" object. It should work !!

vivekDas
  • 1,210
  • 8
  • 11
0

What I usually do is implementing this two method:

The first one add a UITapGestureRecognizer to the whole UIViewController view

func hideKeyboard() {
    let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
    view.addGestureRecognizer(tap)
}

The second one just get called every time the user touch anywhere on the UIViewController's view

func dismissKeyboard() {
    self.view.resignFirstResponder()
}

I add the first one to the viewDidLoad method of the UIViewController. Or better yet if you want to use that on all the app just make that an extension for your UIViewController.

Victor Gil
  • 120
  • 1
  • 3
  • 10
0

How about doing this in viewController, It works for me

func dismissKeyboard() {
    //All the textFields in the form
    let textFields = [textField1, textField2, textField3, textField4, textField5]
    let firstResponder = textFields.first(where: {$0.isFirstResponder ?? false })
    firstResponder?.resignFirstResponder()
}