I had several cases where testers reported that the keyboard would disappear whenever they started typing in some fields in my app. I traced the flow using the simulator and while debugging on a phone and the problem didn't occur, ever. However, when I tried it on an untethered phone it happened fairly consistently.
Here's some pertinent code. All of this is to hide the keyboard when a user taps outside a textfield. My UIViews are subclasses of my Touchview class, which receives all touches:
TouchView.h:
@protocol TouchViewDelegate <NSObject>
-(UIView *) handleTouches:(NSSet *)touches withEvent:(UIEvent *)event inView:(UIView *) view;
@end
@interface TouchView : UIScrollView
@property (nonatomic, strong) id <TouchViewDelegate> touchDelegate;
@end
TouchView.m:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView * touchedView = [super hitTest:point withEvent:event];
NSSet* touches = [event allTouches];
[self.touchDelegate handleTouches:touches withEvent:event inView:touchedView];
return touchedView;
}
I configured the main view as a Touchview and included this in the viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
HMWTouchView * touchView = (HMWTouchView*) self.view;
touchView.touchDelegate = self;
...
}
Here's an implementation of the delegate method:
-(UIView *) handleTouches:(NSSet *)touches withEvent:(UIEvent *)event inView:(UIView *) hitView {
if (![hitView isKindOfClass:[UIButton class]]) {
[[UIResponder firstResponder] resignFirstResponder];
}
return self.view;
}
This looks like it is at least a change in how IOS 8 responds to hits.
Fixed in iOS 8.0.2 No longer necessary.
The problem is that IOS 8.0 (at least) changes how hits are sent to views. Previously, hits within keyboard windows or textfields were absorbed and not passed on to the hitview, which works well for this application. 8.0 changes this, and all hits are sent through.
To fix this, I trapped for keyboard windows within my hit testing code, and I compared the hitView to textfield views to filter those out:
Touchview.m now has the following. The keyboardView method is adapted from this Stack Overflow answer: iOS: How to access the `UIKeyboard`?
-(UIView *) keyboardView {
//Return the current keyboard view
UIWindow * keyboardWindow;
UIView* keyboardView;
UIView* primaryKeyboardView;
for (UIWindow *window in [[UIApplication sharedApplication] windows])
{
if ([NSStringFromClass([window class]) isEqualToString:@"UITextEffectsWindow"])
{
keyboardWindow = window;
break;
}
}
for(int i = 0 ; i < [keyboardWindow.subviews count] ; i++)
{
keyboardView = [keyboardWindow.subviews objectAtIndex:i];
// keyboard found, add the button
if([[keyboardView description] hasPrefix:@"<UIPeripheralHost"] == YES){
primaryKeyboardView = keyboardView;
}
//This code will work on iOS 8.0
else if([[keyboardView description] hasPrefix:@"<UIInputSetContainerView"] == YES){
for(int i = 0 ; i < [keyboardView.subviews count] ; i++)
{
UIView* hostkeyboard = [keyboardView.subviews objectAtIndex:i];
if([[hostkeyboard description] hasPrefix:@"<UIInputSetHost"] == YES){
primaryKeyboardView = hostkeyboard;
}
}
}
}
return primaryKeyboardView;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView * touchedView = [super hitTest:point withEvent:event];
NSSet* touches = [event allTouches];
UIView * keyboardView = [self keyboardView];
if (touchedView != keyboardView) { //Ignore the hit if it's a keyboard
[self.touchDelegate handleTouches:touches withEvent:event inView:touchedView];
}
return touchedView;
}
The new delegate method:
-(UIView *) handleTouches:(NSSet *)touches withEvent:(UIEvent *)event inView:(UIView *) hitView {
if (![hitView isKindOfClass:[UIResponder class]]) {
[[UIResponder firstResponder] resignFirstResponder];
}
return hitView;
}
The firstResponder method is from this gist: https://gist.github.com/vilanovi/e52face5c6f00ce5254d
This still happens in 8.02 for me
I observed the same problem - the keyboard gets dismissed when the user tries to type on it.
My solution is a workaround. To solve the problem I create a dummy empty UIView underneath the keyboard. This dummy view absorbs and consumes tap events and the keyboard is not dismissed. I create the view on the "UIKeyboardDidShowNotification" and remove it on "UIKeyboardWillHideNotification". This gets rid of the problem and works well with screen orientation changes.
Here is the code in my view controller that contains the text fields that show up the keyboard:
/** Dummy view placed underneath the keyboard to prevent its dismissal. */
@property(nonatomic, strong) UIView *dummyView;
In the viewDidLoad
method we register for keyboard show/hide notifications:
// keyboard notifications - we need this to prevent keyboard dismissal
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
Then, after receiving the 'keyboard shown' notification we create the dummy view underneath the keyboard:
- (void)keyboardWasShown:(NSNotification*)notification {
// we need this to prevent keyboard dismissal
NSDictionary* info = [notification userInfo];
CGRect keyboardFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
keyboardFrame = [self.view convertRect:keyboardFrame fromView:nil];
self.dummyView = [[UIView alloc] initWithFrame:keyboardFrame];
[self.view addSubview:self.dummyView];
}
... and remove it when the keyboard is hidden:
- (void)keyboardWillBeHidden:(NSNotification*)notification {
[self.dummyView removeFromSuperview];
self.dummyView = nil;
}