diff options
-rw-r--r-- | src/plugins/platforms/ios/qiosinputcontext.h | 8 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiosinputcontext.mm | 118 |
2 files changed, 76 insertions, 50 deletions
diff --git a/src/plugins/platforms/ios/qiosinputcontext.h b/src/plugins/platforms/ios/qiosinputcontext.h index 533ba686e1..91a6939ad4 100644 --- a/src/plugins/platforms/ios/qiosinputcontext.h +++ b/src/plugins/platforms/ios/qiosinputcontext.h @@ -64,14 +64,14 @@ public: void setFocusObject(QObject *object); void focusWindowChanged(QWindow *focusWindow); - void scrollRootView(); - + void cursorRectangleChanged(); + void scrollToCursor(); + void scroll(int y); private: QIOSKeyboardListener *m_keyboardListener; UIView<UIKeyInput> *m_focusView; - QTransform m_inputItemTransform; bool m_hasPendingHideRequest; - bool m_inSetFocusObject; + QObject *m_focusObject; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm index 39a22f367e..c25d76518f 100644 --- a/src/plugins/platforms/ios/qiosinputcontext.mm +++ b/src/plugins/platforms/ios/qiosinputcontext.mm @@ -49,6 +49,7 @@ QIOSInputContext *m_context; BOOL m_keyboardVisible; BOOL m_keyboardVisibleAndDocked; + BOOL m_ignoreKeyboardChanges; QRectF m_keyboardRect; QRectF m_keyboardEndRect; NSTimeInterval m_duration; @@ -66,6 +67,7 @@ m_context = context; m_keyboardVisible = NO; m_keyboardVisibleAndDocked = NO; + m_ignoreKeyboardChanges = NO; m_duration = 0; m_curve = UIViewAnimationCurveEaseOut; m_viewController = 0; @@ -128,6 +130,8 @@ - (void) keyboardDidChangeFrame:(NSNotification *)notification { + if (m_ignoreKeyboardChanges) + return; m_keyboardRect = [self getKeyboardRect:notification]; m_context->emitKeyboardRectChanged(); @@ -140,11 +144,13 @@ // If the keyboard was visible and docked from before, this is just a geometry // change (normally caused by an orientation change). In that case, update scroll: if (m_keyboardVisibleAndDocked) - m_context->scrollRootView(); + m_context->scrollToCursor(); } - (void) keyboardWillShow:(NSNotification *)notification { + if (m_ignoreKeyboardChanges) + return; // Note that UIKeyboardWillShowNotification is only sendt when the keyboard is docked. m_keyboardVisibleAndDocked = YES; m_keyboardEndRect = [self getKeyboardRect:notification]; @@ -152,15 +158,17 @@ m_duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; m_curve = UIViewAnimationCurve([notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue] << 16); } - m_context->scrollRootView(); + m_context->scrollToCursor(); } - (void) keyboardWillHide:(NSNotification *)notification { + if (m_ignoreKeyboardChanges) + return; // Note that UIKeyboardWillHideNotification is also sendt when the keyboard is undocked. m_keyboardVisibleAndDocked = NO; m_keyboardEndRect = [self getKeyboardRect:notification]; - m_context->scrollRootView(); + m_context->scroll(0); } @end @@ -170,10 +178,10 @@ QIOSInputContext::QIOSInputContext() , m_keyboardListener([[QIOSKeyboardListener alloc] initWithQIOSInputContext:this]) , m_focusView(0) , m_hasPendingHideRequest(false) - , m_inSetFocusObject(false) + , m_focusObject(0) { if (isQtApplication()) - connect(qGuiApp->inputMethod(), &QInputMethod::cursorRectangleChanged, this, &QIOSInputContext::scrollRootView); + connect(qGuiApp->inputMethod(), &QInputMethod::cursorRectangleChanged, this, &QIOSInputContext::cursorRectangleChanged); connect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &QIOSInputContext::focusWindowChanged); } @@ -216,12 +224,14 @@ bool QIOSInputContext::isInputPanelVisible() const return m_keyboardListener->m_keyboardVisible; } -void QIOSInputContext::setFocusObject(QObject *) +void QIOSInputContext::setFocusObject(QObject *focusObject) { - m_inputItemTransform = qApp->inputMethod()->inputItemTransform(); + m_focusObject = focusObject; - if (!m_focusView || !m_focusView.isFirstResponder) + if (!m_focusView || !m_focusView.isFirstResponder) { + scroll(0); return; + } // Since m_focusView is the first responder, it means that the keyboard is open and we // should update keyboard layout. But there seem to be no way to tell it to reread the @@ -230,62 +240,78 @@ void QIOSInputContext::setFocusObject(QObject *) // go, we need to call the super implementation of resignFirstResponder. Since the call // will cause a 'keyboardWillHide' notification to be sendt, we also block scrollRootView // to avoid artifacts: - m_inSetFocusObject = true; + m_keyboardListener->m_ignoreKeyboardChanges = true; SEL sel = @selector(resignFirstResponder); [[m_focusView superclass] instanceMethodForSelector:sel](m_focusView, sel); - m_inSetFocusObject = false; [m_focusView becomeFirstResponder]; + m_keyboardListener->m_ignoreKeyboardChanges = false; + + if (m_keyboardListener->m_keyboardVisibleAndDocked) + scrollToCursor(); } void QIOSInputContext::focusWindowChanged(QWindow *focusWindow) { - UIView<UIKeyInput> *view = reinterpret_cast<UIView<UIKeyInput> *>(focusWindow->handle()->winId()); + UIView<UIKeyInput> *view = focusWindow ? + reinterpret_cast<UIView<UIKeyInput> *>(focusWindow->handle()->winId()) : 0; if ([m_focusView isFirstResponder]) [view becomeFirstResponder]; [m_focusView release]; m_focusView = [view retain]; + + if (view.window != m_keyboardListener->m_viewController.view) + scroll(0); } -void QIOSInputContext::scrollRootView() +void QIOSInputContext::cursorRectangleChanged() { - // Scroll the root view (screen) if: - // - our backend controls the root view controller on the main screen (no hybrid app) - // - the focus object is on the same screen as the keyboard. - // - the first responder is a QUIView, and not some other foreign UIView. - // - the keyboard is docked. Otherwise the user can move the keyboard instead. - // - the inputItem has not been moved/scrolled - if (!isQtApplication() || !m_focusView || m_inSetFocusObject) + if (!m_keyboardListener->m_keyboardVisibleAndDocked) return; - if (m_inputItemTransform != qApp->inputMethod()->inputItemTransform()) { - // The inputItem has moved since the last scroll update. To avoid competing - // with the application where the cursor/inputItem should be, we bail: + // Check if the cursor has changed position inside the input item. Since + // qApp->inputMethod()->cursorRectangle() will also change when the input item + // itself moves, we need to ask the focus object for ImCursorRectangle: + static QPoint prevCursor; + QInputMethodQueryEvent queryEvent(Qt::ImCursorRectangle); + QCoreApplication::sendEvent(m_focusObject, &queryEvent); + QPoint cursor = queryEvent.value(Qt::ImCursorRectangle).toRect().topLeft(); + if (cursor != prevCursor) + scrollToCursor(); + prevCursor = cursor; +} + +void QIOSInputContext::scrollToCursor() +{ + if (!isQtApplication() || !m_focusView) return; - } UIView *view = m_keyboardListener->m_viewController.view; - qreal scrollTo = 0; - - if (m_focusView.isFirstResponder - && m_keyboardListener->m_keyboardVisibleAndDocked - && m_focusView.window == view.window) { - QRectF cursorRect = qGuiApp->inputMethod()->cursorRectangle(); - cursorRect.translate(m_focusView.qwindow->geometry().topLeft()); - qreal keyboardY = m_keyboardListener->m_keyboardEndRect.y(); - int statusBarY = qGuiApp->primaryScreen()->availableGeometry().y(); - const int margin = 20; - - if (cursorRect.bottomLeft().y() > keyboardY - margin) - scrollTo = qMin(view.bounds.size.height - keyboardY, cursorRect.y() - statusBarY - margin); - } + if (view.window != m_focusView.window) + return; - if (scrollTo != view.bounds.origin.y) { - // Scroll the view the same way a UIScrollView works: by changing bounds.origin: - CGRect newBounds = view.bounds; - newBounds.origin.y = scrollTo; - [UIView animateWithDuration:m_keyboardListener->m_duration delay:0 - options:m_keyboardListener->m_curve - animations:^{ view.bounds = newBounds; } - completion:0]; - } + const int margin = 20; + QRectF translatedCursorPos = qApp->inputMethod()->cursorRectangle(); + translatedCursorPos.translate(m_focusView.qwindow->geometry().topLeft()); + qreal keyboardY = m_keyboardListener->m_keyboardEndRect.y(); + int statusBarY = qGuiApp->primaryScreen()->availableGeometry().y(); + + scroll((translatedCursorPos.bottomLeft().y() < keyboardY - margin) ? 0 + : qMin(view.bounds.size.height - keyboardY, translatedCursorPos.y() - statusBarY - margin)); +} + +void QIOSInputContext::scroll(int y) +{ + // Scroll the view the same way a UIScrollView + // works: by changing bounds.origin: + UIView *view = m_keyboardListener->m_viewController.view; + if (y == view.bounds.origin.y) + return; + + CGRect newBounds = view.bounds; + newBounds.origin.y = y; + [UIView animateWithDuration:m_keyboardListener->m_duration delay:0 + options:m_keyboardListener->m_curve + animations:^{ view.bounds = newBounds; } + completion:0]; } + |