46

So I have a UITextView that I'm using to allow the user to submit some text.

My problem is, I can't seem to figure out how to allow the user to 'Cancel' by tapping away from the UITextView.

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
tuzzolotron
  • 718
  • 2
  • 7
  • 11

10 Answers10

107

Simplifying tuzzolotron's answer: Where the following outlet is properly connected in your xib

    IBOutlet UITextView *myTextView;

Use this in the view controller:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    UITouch *touch = [[event allTouches] anyObject];
    if ([myTextView isFirstResponder] && [touch view] != myTextView) {
        [myTextView resignFirstResponder];
    }
    [super touchesBegan:touches withEvent:event];
}

A tap on the View Controller's View, outside of the Text View, will resign the first responder, closing the keyboard.

Community
  • 1
  • 1
ohhorob
  • 11,695
  • 7
  • 41
  • 50
  • I'd be happy to help the downvoter if this isn't working for them.. just leave a comment or start a new question and link to it here. – ohhorob Aug 30 '12 at 05:39
  • 6
    ohhorob, this solution is not working if we click on any other outlet in view, can you help me with that? – TilalHusain Nov 16 '12 at 15:05
  • 1
    In case anybody is still looking at this... I have a problem in that much of my root VIEW is covered by a UIScrollView. If I tap in the margins directly on the UIView, the keyboard will dismiss, but if I tap on that UIScrollView, it will not. – turkeyhundt Oct 22 '14 at 21:25
31

Another approach I used a lot when working with UITableViews, Searchbar and keyboard is this, I think you can apply it to text field

UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hideKeyboard)];
[self.tableView addGestureRecognizer:gestureRecognizer];
gestureRecognizer.cancelsTouchesInView = NO;  // this prevents the gesture recognizers to 'block' touches
[gestureRecognizer release];

And then, here's the simple callback function

- (void)hideKeyboard {
    [myTextField resignFirstResponder];
}

Hope it helps

AndrewR
  • 10,759
  • 10
  • 45
  • 56
Sr.Richie
  • 5,680
  • 5
  • 38
  • 62
  • 2
    You can add to [self view] if you're working with a ViewController. – Ben Flynn Sep 19 '12 at 05:34
  • no it won't work, tables and scrolls view has their own interactions – Softlion Apr 30 '14 at 10:22
  • This answer is the best.Also it works for a group of textfields in a scrollView when I add tapGestureRecognizer to self.view – Okhan Okbay Sep 23 '15 at 19:42
  • This solved the problem. The key here is the third line, which allows other touch events to also happen (by not blocking). It works well with table views too. (In my case, the search bar is located INSIDE a table view cell, and this does not allow for an outlet. In this case, you can do `self.view.endEditing(true)` instead of resignFirstResponder. – Seop Yoon Dec 26 '17 at 04:50
7

In my view:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];

    // pass touches up to viewController
    [self.nextResponder touchesBegan:touches withEvent:event];
}

In my viewcontroller:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    UITouch *touch = [[event allTouches] anyObject];
    if (keyboardShown && [touch view] != self.TextView) 
    {
         [self.TextView resignFirstResponder];
    }
    [super touchesBegan:touches withEvent:event];
}

- (void)keyboardWasShown:(NSNotification*)aNotification
{    
        keyboardShown = YES;
}
Jason Plank
  • 2,336
  • 5
  • 31
  • 40
tuzzolotron
  • 718
  • 2
  • 7
  • 11
  • 1
    How do I add the touchesBegan method to a UITableView that I've made a subview of self.view of the UIViewController I'm working in. Do I have to subclass UITableView, i.e., @interface TouchableTableView : UITableView? Or, is there an easier way to do it w/o subclassing UITableView? – ma11hew28 Aug 14 '10 at 19:56
3

This is an old post, but I was implementing this solution when i found something a lot simpler (maybe it wasn't available back then).

[self.myTextView endEditing:YES];

I'm using a UITableView, so I put this line on the didSelectRowAtIndexPath: method. So far, so good...

Carlos B
  • 456
  • 3
  • 11
2

Here is a better solution that does not depend upon an outlet and is going to work with any number of UITextField objects in your controller.

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    UITouch *touch = [[event allTouches] anyObject];

    if (![[touch view] isKindOfClass:[UITextField class]]) {
        [self.view endEditing:YES];
    }
    [super touchesBegan:touches withEvent:event];
}
Yas Tabasam
  • 10,517
  • 9
  • 48
  • 53
  • don't think you need to check for UITextField - even if you `endEditing:` indiscriminately, switching between text fields still works, keyboard does stay up – Nas Banov Nov 14 '13 at 00:01
2
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
    var touch = event.allTouches()?.anyObject() as UITouch

    if( textfield.isFirstResponder() && touch.view != textfield) {
        textfield.resignFirstResponder()
        NSLog("Resigning first responder since touches began outside")
    }

    super.touchesBegan(touches, withEvent: event)
}

ohhorob's answer worked for me too. Here's the simple swift equivalent.

atomic_ice
  • 83
  • 5
2

Here is the swift 3 code

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let touch = touches.first {
        if textField.isFirstResponder && touch.view != textField {
            textField.resignFirstResponder()
        }
    }
    super.touchesBegan(touches, with: event)
}
mythicalcoder
  • 3,143
  • 1
  • 32
  • 42
1

Very easy

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.view endEditing:YES];
}
MD SHAHIDUL ISLAM
  • 14,325
  • 6
  • 82
  • 89
0

I wanted a version of this that worked for resigning from any view, without requiring specific outlets for each one.. Here is my solution, which I put into one of my standard container views..

It's using MonoTouch C#, though it should be obvious how to convert it to Objective-C

    private void findAndResignFirstResponder(UIView node=null) {
        if (node == null) { return; }
        if (node.IsFirstResponder) {
            node.ResignFirstResponder();
            return;
        }

        foreach (UIView view in node.Subviews) {
            findAndResignFirstResponder(node:view);
        }
    }

    private bool haveDrag = false;
    public override void TouchesEnded(NSSet touches, UIEvent evt) {
        if (!haveDrag) {
            UITouch touch = (UITouch)evt.AllTouches.AnyObject;
            if (!touch.View.CanBecomeFirstResponder) {
                this.findAndResignFirstResponder(this);
                Console.WriteLine("resign");
            }
        }
        base.TouchesEnded (touches, evt);
    }
    public override void TouchesMoved(NSSet touches, UIEvent evt) {
        haveDrag = true;
        base.TouchesMoved (touches, evt);
    }

    public override void TouchesBegan(NSSet touches, UIEvent evt) {
        haveDrag = false;
        base.TouchesBegan (touches, evt);
    }
David Jeske
  • 2,306
  • 24
  • 29
0

My way to do it...

  • Add a reference to the TextView in the @interface of your ViewController:

    @property (weak, nonatomic) IBOutlet UITextView *yourTextView;
    
  • Override this method in the @implementation of your ViewController:

    - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
    {
        if ([self.yourTextView isFirstResponder]) {
          [self.yourTextView resignFirstResponder];
        }
    }