1

How to call a custom function when the user touches outside of the UITextField that has keyboard focus?

My goal is to call a function for textfield validation.

I tried this:

// Dismiss Keyboard

- (void)touchesEnded: (NSSet *)touches withEvent: (UIEvent *)event {
    for (UIView* view in self.view.subviews) {
        if ([view isKindOfClass:[UITextField class]]) 
        {
            [view resignFirstResponder];
            [self validationForNoOfPassengers];
        }

    }
}

validationForNoOfPassengers is my method to check for validation. The problem here is that when I tap on other UI controls it does not call the above method. Above method is called only when touched outside on the screen (but not on UIcontrols). Any guidance?

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
user1140780
  • 988
  • 2
  • 13
  • 29

4 Answers4

2

Set your view controller (or some other object) to be the text field's delegate. Do the validation in -textFieldShouldEndEditing: or -textFieldDidEndEditing:.

Caleb
  • 124,013
  • 19
  • 183
  • 272
  • 1
    Thanks. But still when I am tapping on other controls say buttons or segmented control. This function is not called. – user1140780 Jan 11 '12 at 21:19
2

EDIT

See my answer here for a better, general purpose solution that you can drop into any project.

ORIGINAL ANSWER

Other answers have suggested doing your validation in the text field delegate's textFieldShouldEndEditing: or textFieldDidEndEditing: methods. As you have discovered, when you touch a control, your text field doesn't send those messages to its delegate.

It doesn't send those messages because it is not ending editing. The text field remains first responder (the keyboard focus) when you touch another non-text control.

I don't know if you have just one or two text fields that have validation, or a lot of text fields that have validations. I don't know if you have just one or two non-text controls or a lot of non-text controls. The best solution for you really depends on what you have.

What you'd really like to do is check, on each new touch, whether there is a first responder, if if so, whether the new touch is outside the first responder. If so, send resignFirstResponder to the current first responder (which will make it send textFieldShouldEndEditing: to its delegate), and only proceed if resignFirstResponder returns YES.

If you only have one text field, you can make your top-level view be a subclass of UIView and override the hitTest:withEvent: method, like this:

MyView.h

#import <UIKit/UIKit.h>
@interface MyView : UIView
@property (weak, nonatomic) IBOutlet UITextField *textField;
@end

MyView.m

#import "MyView.h"
@implementation MyView
@synthesize textField;
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *hitView = [super hitTest:point withEvent:event];
    return (hitView != self.textField
        && self.textField.isFirstResponder
        && ![self.textField resignFirstResponder])
        ? nil : hitView;
}
@end

It turns out that hitTest:withEvent: is actually called several times (three in my testing) per touch. So you don't just want to present an alert in textFieldShouldEndEditing: if the validation fails - you need to keep track of whether the alert is already on screen. Otherwise you will see the alert pop up repeatedly.

The above method will get ugly if you have several text fields. You will have to check each one to see if it is currently first responder and not the touched view. It would be much easier if you could just ask the system for the current first responder.

There is a method that will return the current first responder: you can send firstResponder to [[UIApplication sharedApplication] keyWindow] to get the current first responder (or null if there is no first responder). Unfortunately, the firstResponder message is a public API. If you use it, you might not be allowed into the App Store.

If you decide you want to use it, here's what you put in MyView.m:

MyView.m (revised)

#import "MyView.h"

@protocol MyViewFirstResponderProtocol
// Have to declare the message so the compiler will allow it.
- (UIResponder *)firstResponder;
@end

@implementation MyView
@synthesize textField;
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    NSLog(@"hitTest for event %@", event);
    UIView *hitView = [super hitTest:point withEvent:event];
    UIResponder *firstResponder = [(id)self.window firstResponder];
    return (firstResponder
        && hitView != firstResponder
        && ![firstResponder resignFirstResponder])
        ? nil : hitView;
}
@end
Community
  • 1
  • 1
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
0

Set a delegate to your text field and implement the textFieldDidEndEditing: method (in a class that conforms to the UITextFieldDelegate protocol) and call your function in it:

void
do_something(void)
{
    return;
}

/* ... */

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    do_something();
}
sidyll
  • 57,726
  • 14
  • 108
  • 151
  • Thanks. But still when I am tapping on other controls say buttons or segmented control. This function is not called. – user1140780 Jan 11 '12 at 21:06
0

Try overriding resignFirstResponder and performing your validation there.

Mark Adams
  • 30,776
  • 11
  • 77
  • 77