I've just shipped an iOS 8 update to my crossword app, Four Down, and have started receiving reports that it gets into a state where answers can't be entered as tapping the keyboard dismisses it rather than entering a letter.
After some investigation, it reliably gets into this state whenever the app is put into the background and then becomes active again. Quitting the app and relaunching it fixes the problem until it's next brought back from the background.
When the problem's occurring it appears that things are stacked in the wrong order for touch detection (but not visually) as the view visually beneath the keyboard is receiving the touch. I believe the problem's iOS 8 specific.
In an attempt to diagnose the problem, I've subclassed UIApplication and overridden sendEvent so that I can examine the events being sent. When the problem's occurring, the event is assigned to the wrong UIWindow. Here's the event when it's working:
2014-10-04 07:56:57.998 four-down[95709:8557144] <UITouchesEvent: 0x7fe838f07350> timestamp: 1.29014e+06 touches: {(
<UITouch: 0x7fe83a11abf0> phase: Began tap count: 1 window: <UITextEffectsWindow: 0x7fe838d549e0; frame = (0 0; 320 568); opaque = NO; gestureRecognizers = <NSArray: 0x7fe838d556d0>; layer = <UIWindowLayer: 0x7fe838d54ea0>> view: <UIKeyboardLayoutStar: 0x7fe838da6a90; frame = (0 0; 320 216); opaque = NO; layer = <CALayer: 0x7fe838db3cc0>> location in window: {243, 388.5} previous location in window: {243, 388.5} location in view: {243, 36.5} previous location in view: {243, 36.5}
)}
And here's the event when it's not working:
2014-10-04 07:58:08.952 four-down[95709:8557144] <UITouchesEvent: 0x7fe838f07350> timestamp: 1.29021e+06 touches: {(
<UITouch: 0x7fe83a5c91a0> phase: Began tap count: 1 window: <UIWindow: 0x7fe83a33f3f0; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x7fe83a340010>; layer = <UIWindowLayer: 0x7fe83a304160>> view: <CrosswordInteractiveView: 0x7fe83a555920; frame = (0 65; 310 310); gestureRecognizers = <NSArray: 0x7fe83a54d4e0>; layer = <CALayer: 0x7fe83a544df0>> location in window: {241.5, 379} previous location in window: {241.5, 379} location in view: {236.5, 250} previous location in view: {236.5, 250}
)}
Note the different window on the two events. UITextEffectsWindow is the window containing the keyboard, and UIWindow is the main application window. I've checked the windowLevel property on each window. It's the same in both cases: 1.0 for the keyboard window and 0.0 for the main application window.
What, if anything, am I doing wrong? Given that it works on iOS 7, it feels like an iOS bug but perhaps there's a new iOS 8 API that I should be calling? If there's not, I'd welcome suggestions for a workaround
I filed a radar (18546966) with Apple. They closed it as a duplicate of 18406902. 18406902 is closed, apparently, but that's all the information I have. No idea what version of iOS the fix will be in. It may be fixed in the iOS 9 betas – I haven't tested it yet.
In the meantime, I figured out the following workaround which has stopped the bug reports:
- (UIView*) hitTest:(CGPoint) point withEvent:(UIEvent*) event {
UIView* hitView = [super hitTest:point withEvent:event];
if (!hitView) {
UIWindow* keyboardWindow = [self findKeyboardWindow];
if (keyboardWindow) {
UIView* viewInKeyboardWindow = [keyboardWindow hitTest:point withEvent:event];
if (!viewInKeyboardWindow) {
[self resignFirstResponder];
} else {
return viewInKeyboardWindow;
}
} else {
[self resignFirstResponder];
}
}
return hitView;
}
- (UIWindow*) findKeyboardWindow {
NSArray* windows = [UIApplication sharedApplication].windows;
if (windows.count == 2) {
for (UIWindow* candidate in windows) {
if (candidate != self.window) {
return candidate;
}
}
}
return nil;
}