I am trying to determine when a user taps on some area other than the single UITextView I have on the screen. This is similar to this question about UITableViews, but I have a few problems with the solutions presented there. When the keyboard is dismissed, I scroll the screen a bit to hide where the keyboard was. My problem is that when I use UITapGestureRecognizer to determine if the screen was tapped, the tap doesn't go through to the other controls on the screen. I am using gestureRecognizer.cancelsTouchesInView = NO, it's a problem with the timing. The screen scrolls away before the control recognizes that it was clicked. Any idea how I can solve the problem? I'm perfectly happy using something other than gesture recognition.
3 Answers
I've used a custom (invisible) button as the background layer to do this in the past.

- 4,743
- 1
- 20
- 33
-
I've done that as well, but I've got subviews laid out such that I'd have to add several buttons to make it work (as well as including keyboard handling in my regular button callbacks), and I need this concept in multiple screens, so I'd like to find a more general solution. – Micah Hainline Mar 01 '11 at 03:16
-
Do those subviews need to receive clicks? Could the invisible button layer be and in between layer? I hear you, though, I'm sure there's an elegant general solution. – Devin Ceartas Mar 01 '11 at 03:19
-
I'll keep looking and post the results if I can come up with something – Micah Hainline Mar 02 '11 at 15:17
Keep the gesture recognizer. Have it use a method like this:
- (void)dismissKeyboard:(UIGestureRecognizer *)gesture
{
[self.view endEditing:NO];
}

- 30,776
- 11
- 77
- 77
I found the solution to the problem. I'm still using the UITapGestureRecognizer, but I now have a zero-length delay before hiding the keyboard, which allows the tap event to propagate correctly to the subviews, such as buttons. The basic view is a text field and button controls filling the screen. To allow the screen to be viewed correctly when the keyboard is shown, the whole thing is wrapped in a scroll view, and a placeholder view for the area the keyboard takes up is added to the bottom. It is only expanded when the keyboard is shown. Here are all the relevant pieces of code, which should allow anyone to implement the tap-anywhere-to-dismiss-keyboard idea as well as solving the problem of controls being hidden by the keyboard:
- (void)viewDidLoad {
[super viewDidLoad];
...
UITapGestureRecognizer *tapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(hideKeyboardWithDelay)] autorelease];
tapRecognizer.cancelsTouchesInView = NO;
[self.view addGestureRecognizer: tapRecognizer];
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(keyboardWillBeShown:) name: UIKeyboardWillShowNotification object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(keyboardWillBeHidden:) name: UIKeyboardWillHideNotification object: nil];
}
- (void) hideKeyboardWithDelay {
// using afterDelay allows the event to go through to any button before scrolling
[self performSelector: @selector(hideKeyboard) withObject: nil afterDelay: 0.0f];
}
- (void) hideKeyboard {
[self.myTextField1 resignFirstResponder];
[self.myTextField2 resignFirstResponder];
}
- (void) keyboardWillBeShown: (NSNotification *) notification {
NSDictionary* info = [notification userInfo];
CGSize keyboardSize = [[info objectForKey: UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGRect frame = self.keyboardPlaceholder.frame;
self.keyboardPlaceholder.frame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, keyboardSize.height);
CGFloat contentHeight = self.scrollView.contentSize.height + keyboardSize.height + 20; // in my case, save 20px for the status bar
self.scrollView.contentSize = CGSizeMake(frame.size.width, contentHeight);
[self.scrollView scrollRectToVisible: self.keyboardPlaceholder.frame animated: YES];
}
- (void) keyboardWillBeHidden: (NSNotification *) notification {
CGRect frame = self.keyboardPlaceholder.frame;
NSDictionary* info = [notification userInfo];
NSValue* value = [info objectForKey:UIKeyboardAnimationDurationUserInfoKey];
NSTimeInterval duration = 0.3f; // default keyboard animation time
[value getValue: &duration];
[UIView beginAnimations: @"hideKeyboardAnimation" context: nil];
[UIView setAnimationDuration: duration];
[UIView setAnimationCurve: UIViewAnimationCurveEaseInOut];
self.keyboardPlaceholder.frame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, 0);
self.scrollView.contentSize = self.view.frame.size;
[UIView commitAnimations];
}
- (void)viewDidUnload {
[[NSNotificationCenter defaultCenter] removeObserver: self];
[super viewDidUnload];
}
Hope someone finds this useful.

- 14,367
- 9
- 52
- 85