Dismiss keyboard on touch anywhere outside UITextField
I normally use the following
First:
Include the following extension in your implementation file, findFirstResponder
will help you find the first resonder
@implementation UIView (FindFirstResponder)
- (UIView*)findFirstResponder
{
if (self.isFirstResponder) {
return self;
}
for (UIView *subView in self.subviews) {
UIView *responder = [subView findFirstResponder];
if (responder)
return responder;
}
return nil;
}
@end
Then in your view controller viewDidLoad
add the following
- (void)viewDidLoad
{
[super viewDidLoad];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification
object:nil];
[center addObserver:self selector:@selector(keyboardDidHide:)
name:UIKeyboardWillHideNotification
object:nil];
// Do any additional setup after loading the view, typically from a nib.
}
The notification functions will be like this
- (void) keyboardDidShow:(NSNotification*)notification
{
UIButton *button = [[UIButton alloc] init];
CGRect rect = self.view.bounds;
button.frame = rect;
button.backgroundColor = [UIColor blackColor];
button.tag = 111;
UIView *currentResponer = [self.view findFirstResponder];
[button addTarget:currentResponer action:@selector(resignFirstResponder) forControlEvents:UIControlEventTouchUpInside];
[self.view insertSubview:button belowSubview:currentResponer];
}
- (void) keyboardDidHide:(NSNotification*)notification
{
[[self.view viewWithTag:111] removeFromSuperview];
}
When the keyboard shows, i add a UIButton
beneath the current first responder, this button action will hide the keyboard,
A limitation here is that the UITextField
has to be in self.view
however you could adapt this technique for your need with some modifications, hope it helps you
Here is a much easier and efficient way of dealing with that. This is gonna work for any UITextField in your view controller. You can even add it to your base view controller (if you have got one) and it will work like a charm.
- (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];
}
Your view hierarchy lives inside a UIWindow
. The UIWindow
is responsible for forwarding touch events to the correct view in its sendEvent:
method. Let's make a subclass of UIWindow
to override sendEvent:
.
@interface MyWindow : UIWindow
@end
The window will need a reference to the current first responder, if there is one. You might decide to also use UITextView
, so we'll observe notifications from both text fields and text views.
@implementation MyWindow {
UIView *currentFirstResponder_;
}
- (void)startObservingFirstResponder {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(observeBeginEditing:) name:UITextFieldTextDidBeginEditingNotification object:nil];
[center addObserver:self selector:@selector(observeEndEditing:) name:UITextFieldTextDidEndEditingNotification object:nil];
[center addObserver:self selector:@selector(observeBeginEditing:) name:UITextViewTextDidBeginEditingNotification object:nil];
[center addObserver:self selector:@selector(observeEndEditing:) name:UITextViewTextDidEndEditingNotification object:nil];
}
- (void)stopObservingFirstResponder {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self name:UITextFieldTextDidBeginEditingNotification object:nil];
[center removeObserver:self name:UITextFieldTextDidEndEditingNotification object:nil];
[center removeObserver:self name:UITextViewTextDidBeginEditingNotification object:nil];
[center removeObserver:self name:UITextViewTextDidEndEditingNotification object:nil];
}
- (void)observeBeginEditing:(NSNotification *)note {
currentFirstResponder_ = note.object;
}
- (void)observeEndEditing:(NSNotification *)note {
if (currentFirstResponder_ == note.object) {
currentFirstResponder_ = nil;
}
}
The window will start observing the notifications when it's initialized, and stop when it's deallocated:
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self commonInit];
}
return self;
}
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
[self commonInit];
}
return self;
}
- (void)commonInit {
[self startObservingFirstResponder];
}
- (void)dealloc {
[self stopObservingFirstResponder];
}
We'll override sendEvent:
to “adjust” the first responder based on the event, and then call super's sendEvent:
to send the event normally.
- (void)sendEvent:(UIEvent *)event {
[self adjustFirstResponderForEvent:event];
[super sendEvent:event];
}
We don't need to do anything about the first responder if there is no first responder. If there is a first responder, and it contains a touch, we don't want to force it to resign. (Remember, there can be multiple touches simultaneously!) If there is a first responder, and a new touch appears in another view that can become the first responder, the system will handle that correctly automatically, so we also want to ignore that case. But if there is a first responder, and it doesn't contain any touches, and a new touch appears in a view that can't become first responder, we want to make the first responder resign.
- (void)adjustFirstResponderForEvent:(UIEvent *)event {
if (currentFirstResponder_
&& ![self eventContainsTouchInFirstResponder:event]
&& [self eventContainsNewTouchInNonresponder:event]) {
[currentFirstResponder_ resignFirstResponder];
}
}
Reporting whether an event contains a touch in the first responder is easy:
- (BOOL)eventContainsTouchInFirstResponder:(UIEvent *)event {
for (UITouch *touch in [event touchesForWindow:self]) {
if (touch.view == currentFirstResponder_)
return YES;
}
return NO;
}
Reporting whether an event contains a new touch in a view that can't become first responder is almost as easy:
- (BOOL)eventContainsNewTouchInNonresponder:(UIEvent *)event {
for (UITouch *touch in [event touchesForWindow:self]) {
if (touch.phase == UITouchPhaseBegan && ![touch.view canBecomeFirstResponder])
return YES;
}
return NO;
}
@end
Once you've implemented this class, you need to change your app to use it instead of UIWindow
.
If you're creating your UIWindow
in application:didFinishLaunchingWithOptions:
, you need to #import "MyWindow.h"
at the top of your AppDelegate.m
, and then change application:didFinishLaunchingWithOptions:
to create a MyWindow
instead of a UIWindow
.
If you're creating your UIWindow
in a nib, you need to set the custom class of the window to MyWindow
in the nib.