From 370d7bf48afb20017852c0c7faff45d40dbf81a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Wed, 22 Oct 2014 13:46:35 +0200 Subject: iOS: Make hide-keyboard gesture use normal UIGestureRecognizer flow Allows us to track state through the normal gesture recognizer states instead of custom variables. Change-Id: I4fe1b370a581132a9bbb8f51f7bee73381b80341 Reviewed-by: Richard Moe Gustavsen --- src/plugins/platforms/ios/qiosinputcontext.mm | 131 ++++++++++++++++++-------- 1 file changed, 90 insertions(+), 41 deletions(-) diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm index 25d9f6c421..acdf6d9715 100644 --- a/src/plugins/platforms/ios/qiosinputcontext.mm +++ b/src/plugins/platforms/ios/qiosinputcontext.mm @@ -46,25 +46,28 @@ #include "qiosglobal.h" #include "qiosintegration.h" #include "qiostextresponder.h" +#include "qiosviewcontroller.h" #include "qioswindow.h" #include "quiview.h" #include #include +// ------------------------------------------------------------------------- + static QUIView *focusView() { return qApp->focusWindow() ? reinterpret_cast(qApp->focusWindow()->winId()) : 0; } -@interface QIOSKeyboardListener : UIGestureRecognizer { +// ------------------------------------------------------------------------- + +@interface QIOSKeyboardListener : UIGestureRecognizer { @public QIOSInputContext *m_context; BOOL m_keyboardVisible; BOOL m_keyboardVisibleAndDocked; - BOOL m_touchPressWhileKeyboardVisible; - BOOL m_keyboardHiddenByGesture; QRectF m_keyboardRect; CGRect m_keyboardEndRect; NSTimeInterval m_duration; @@ -77,13 +80,11 @@ static QUIView *focusView() - (id)initWithQIOSInputContext:(QIOSInputContext *)context { - self = [super initWithTarget:self action:@selector(gestureTriggered)]; + self = [super initWithTarget:self action:@selector(gestureStateChanged:)]; if (self) { m_context = context; m_keyboardVisible = NO; m_keyboardVisibleAndDocked = NO; - m_touchPressWhileKeyboardVisible = NO; - m_keyboardHiddenByGesture = NO; m_duration = 0; m_curve = UIViewAnimationCurveEaseOut; m_viewController = 0; @@ -99,10 +100,9 @@ static QUIView *focusView() Q_ASSERT(m_viewController); // Attach 'hide keyboard' gesture to the window, but keep it disabled when the - // keyboard is not visible. Note that we never trigger the gesture the way it is intended - // since we don't want to cancel touch events and interrupt flicking etc. Instead we use - // the gesture framework more as an event filter and hide the keyboard silently. + // keyboard is not visible. self.enabled = NO; + self.cancelsTouchesInView = NO; self.delaysTouchesEnded = NO; [m_viewController.view.window addGestureRecognizer:self]; } @@ -156,11 +156,19 @@ static QUIView *focusView() // Note that UIKeyboardWillShowNotification is only sendt when the keyboard is docked. m_keyboardVisibleAndDocked = YES; m_keyboardEndRect = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; - self.enabled = YES; + if (!m_duration) { m_duration = [[notification.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; m_curve = UIViewAnimationCurve([[notification.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue]); } + + UIResponder *firstResponder = [UIResponder currentFirstResponder]; + if (![firstResponder isKindOfClass:[QIOSTextInputResponder class]]) + return; + + // Enable hide-keyboard gesture + self.enabled = YES; + m_context->scrollToCursor(); } @@ -169,7 +177,7 @@ static QUIView *focusView() // Note that UIKeyboardWillHideNotification is also sendt when the keyboard is undocked. m_keyboardVisibleAndDocked = NO; m_keyboardEndRect = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; - if (!m_keyboardHiddenByGesture) { + if (self.state != UIGestureRecognizerStateBegan) { // Only disable the gesture if the hiding of the keyboard was not caused by it. // Otherwise we need to await the final touchEnd callback for doing some clean-up. self.enabled = NO; @@ -202,54 +210,81 @@ static QUIView *focusView() } } -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event +// ------------------------------------------------------------------------- + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { - CGPoint p = [[touches anyObject] locationInView:m_viewController.view.window]; - if (CGRectContainsPoint(m_keyboardEndRect, p)) { - m_keyboardHiddenByGesture = YES; + [super touchesBegan:touches withEvent:event]; - UIResponder *firstResponder = [UIResponder currentFirstResponder]; - Q_ASSERT([firstResponder isKindOfClass:[QIOSTextInputResponder class]]); - [firstResponder resignFirstResponder]; - } + Q_ASSERT(m_keyboardVisibleAndDocked); - [super touchesMoved:touches withEvent:event]; + if ([touches count] != 1) + self.state = UIGestureRecognizerStateFailed; } -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { - Q_ASSERT(m_keyboardVisibleAndDocked); - m_touchPressWhileKeyboardVisible = YES; - [super touchesBegan:touches withEvent:event]; + [super touchesMoved:touches withEvent:event]; + + if (self.state != UIGestureRecognizerStatePossible) + return; + + CGPoint touchPoint = [[touches anyObject] locationInView:m_viewController.view.window]; + if (CGRectContainsPoint(m_keyboardEndRect, touchPoint)) + self.state = UIGestureRecognizerStateBegan; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { - m_touchPressWhileKeyboardVisible = NO; - [self performSelectorOnMainThread:@selector(touchesEndedPostDelivery) withObject:nil waitUntilDone:NO]; [super touchesEnded:touches withEvent:event]; + + [self touchesEndedOrCancelled]; } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { - m_touchPressWhileKeyboardVisible = NO; - [self performSelectorOnMainThread:@selector(touchesEndedPostDelivery) withObject:nil waitUntilDone:NO]; [super touchesCancelled:touches withEvent:event]; + + [self touchesEndedOrCancelled]; } -- (void)touchesEndedPostDelivery +- (void)touchesEndedOrCancelled { - // Do some clean-up _after_ touchEnd has been delivered to QUIView - m_keyboardHiddenByGesture = NO; + // Defer final state change until next runloop iteration, so that Qt + // has a chance to process the final touch events first, before we eg. + // scroll the view. + dispatch_async(dispatch_get_main_queue (), ^{ + // iOS will transition from began to changed by itself + Q_ASSERT(self.state != UIGestureRecognizerStateBegan); + + if (self.state == UIGestureRecognizerStateChanged) + self.state = UIGestureRecognizerStateEnded; + else + self.state = UIGestureRecognizerStateFailed; + }); +} + +- (void)gestureStateChanged:(id)sender +{ + Q_UNUSED(sender); + + if (self.state == UIGestureRecognizerStateBegan) { + qImDebug() << "hide keyboard gesture was triggered"; + UIResponder *firstResponder = [UIResponder currentFirstResponder]; + Q_ASSERT([firstResponder isKindOfClass:[QIOSTextInputResponder class]]); + [firstResponder resignFirstResponder]; + } +} + +- (void)reset +{ + [super reset]; + if (!m_keyboardVisibleAndDocked) { + qImDebug() << "keyboard was hidden, disabling hide-keyboard gesture"; self.enabled = NO; - if (qApp->focusObject()) { - // UI Controls are told to gain focus on touch release. So when the 'hide keyboard' gesture - // finishes, the final touch end can trigger a control to gain focus. This is in conflict with - // the gesture, so we clear focus once more as a work-around. - static_cast(QObjectPrivate::get(qApp->focusWindow()))->clearFocusObject(); - } } else { + qImDebug() << "gesture completed without triggering, scrolling view to cursor"; m_context->scrollToCursor(); } } @@ -353,10 +388,10 @@ void QIOSInputContext::scrollToCursor() if (!isQtApplication()) return; - if (m_keyboardListener->m_touchPressWhileKeyboardVisible) { - // Don't scroll to the cursor if the user is touching the screen. This - // interferes with selection and the 'hide keyboard' gesture. Instead - // we update scrolling upon touchEnd. + if (m_keyboardListener.state == UIGestureRecognizerStatePossible && m_keyboardListener.numberOfTouches == 1) { + // Don't scroll to the cursor if the user is touching the screen and possibly + // trying to trigger the hide-keyboard gesture. + qImDebug() << "preventing scrolling to cursor as we're still waiting for a possible gesture"; return; } @@ -426,6 +461,18 @@ void QIOSInputContext::setFocusObject(QObject *focusObject) { Q_UNUSED(focusObject); + qImDebug() << "new focus object =" << focusObject; + + if (m_keyboardListener.state == UIGestureRecognizerStateChanged) { + // A new focus object may be set as part of delivering touch events to + // application during the hide-keyboard gesture, but we don't want that + // to result in a new object getting focus and bringing the keyboard up + // again. + qImDebug() << "clearing focus object" << focusObject << "as hide-keyboard gesture is active"; + clearCurrentFocusObject(); + return; + } + reset(); if (m_keyboardListener->m_keyboardVisibleAndDocked) @@ -436,6 +483,8 @@ void QIOSInputContext::focusWindowChanged(QWindow *focusWindow) { Q_UNUSED(focusWindow); + qImDebug() << "new focus window =" << focusWindow; + reset(); [m_keyboardListener handleKeyboardRectChanged]; -- cgit v1.2.3