iOS: Make QIOSTextInputResponder a proper first-responder during text input

Instead of faking it, by returning YES for isFirstResponder, which caused
issues when iOS would try to dismiss the keyboard by resigning the true
first-responder.

Change-Id: I816c4cf9c699d72995ce7968e1f1a4aa9c9c167e
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@digia.com>
This commit is contained in:
Tor Arne Vestbø 2014-10-22 13:21:34 +02:00
parent d8b45a360f
commit d563f6142b
4 changed files with 105 additions and 10 deletions

View File

@ -65,7 +65,8 @@ public:
void showInputPanel();
void hideInputPanel();
void hideVirtualKeyboard();
void clearCurrentFocusObject();
bool isInputPanelVisible() const;
void setFocusObject(QObject *object);
@ -81,10 +82,15 @@ public:
const ImeState &imeState() { return m_imeState; };
bool isReloadingInputViewsFromUpdate() const { return m_isReloadingInputViewsFromUpdate; }
static QIOSInputContext *instance();
private:
QIOSKeyboardListener *m_keyboardListener;
QIOSTextInputResponder *m_textResponder;
ImeState m_imeState;
bool m_isReloadingInputViewsFromUpdate;
};
QT_END_NAMESPACE

View File

@ -44,6 +44,7 @@
#import <UIKit/UIGestureRecognizerSubclass.h>
#include "qiosglobal.h"
#include "qiosintegration.h"
#include "qiostextresponder.h"
#include "qioswindow.h"
#include "quiview.h"
@ -206,7 +207,10 @@ static QUIView *focusView()
CGPoint p = [[touches anyObject] locationInView:m_viewController.view.window];
if (CGRectContainsPoint(m_keyboardEndRect, p)) {
m_keyboardHiddenByGesture = YES;
m_context->hideVirtualKeyboard();
UIResponder *firstResponder = [UIResponder currentFirstResponder];
Q_ASSERT([firstResponder isKindOfClass:[QIOSTextInputResponder class]]);
[firstResponder resignFirstResponder];
}
[super touchesMoved:touches withEvent:event];
@ -279,10 +283,16 @@ Qt::InputMethodQueries ImeState::update(Qt::InputMethodQueries properties)
// -------------------------------------------------------------------------
QIOSInputContext *QIOSInputContext::instance()
{
return static_cast<QIOSInputContext *>(QIOSIntegration::instance()->inputContext());
}
QIOSInputContext::QIOSInputContext()
: QPlatformInputContext()
, m_keyboardListener([[QIOSKeyboardListener alloc] initWithQIOSInputContext:this])
, m_textResponder(0)
, m_isReloadingInputViewsFromUpdate(false)
{
if (isQtApplication())
connect(qGuiApp->inputMethod(), &QInputMethod::cursorRectangleChanged, this, &QIOSInputContext::cursorRectangleChanged);
@ -310,9 +320,10 @@ void QIOSInputContext::hideInputPanel()
// No-op, keyboard controlled fully by platform based on focus
}
void QIOSInputContext::hideVirtualKeyboard()
void QIOSInputContext::clearCurrentFocusObject()
{
static_cast<QWindowPrivate *>(QObjectPrivate::get(qApp->focusWindow()))->clearFocusObject();
if (QWindow *focusWindow = qApp->focusWindow())
static_cast<QWindowPrivate *>(QObjectPrivate::get(focusWindow))->clearFocusObject();
}
bool QIOSInputContext::isInputPanelVisible() const
@ -452,12 +463,24 @@ void QIOSInputContext::update(Qt::InputMethodQueries updatedProperties)
updatedProperties |= (Qt::ImHints | Qt::ImPlatformData);
}
qImDebug() << "fw =" << qApp->focusWindow() << "fo =" << qApp->focusObject();
Qt::InputMethodQueries changedProperties = m_imeState.update(updatedProperties);
if (changedProperties & (Qt::ImEnabled | Qt::ImHints | Qt::ImPlatformData)) {
// Changes to enablement or hints require virtual keyboard reconfigure
[m_textResponder release];
m_textResponder = [[QIOSTextInputResponder alloc] initWithInputContext:this];
[m_textResponder reloadInputViews];
qImDebug() << "changed IM properties" << changedProperties << "require keyboard reconfigure";
if (inputMethodAccepted()) {
qImDebug() << "replacing text responder with new text responder";
[m_textResponder autorelease];
m_textResponder = [[QIOSTextInputResponder alloc] initWithInputContext:this];
[m_textResponder becomeFirstResponder];
} else {
qImDebug() << "IM not enabled, reloading input views";
QScopedValueRollback<bool> recursionGuard(m_isReloadingInputViewsFromUpdate, true);
[[UIResponder currentFirstResponder] reloadInputViews];
}
} else {
[m_textResponder notifyInputDelegate:changedProperties];
}
@ -497,6 +520,12 @@ void QIOSInputContext::commit()
@implementation QUIView (InputMethods)
- (void)reloadInputViews
{
qApp->inputMethod()->reset();
if (QIOSInputContext::instance()->isReloadingInputViewsFromUpdate()) {
qImDebug() << "preventing recursion by reloading super";
[super reloadInputViews];
} else {
qImDebug() << "reseting input methods";
qApp->inputMethod()->reset();
}
}
@end

View File

@ -218,11 +218,61 @@
[super dealloc];
}
- (BOOL)isFirstResponder
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (BOOL)becomeFirstResponder
{
FirstResponderCandidate firstResponderCandidate(self);
qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
if (![super becomeFirstResponder]) {
qImDebug() << self << "was not allowed to become first responder";
return NO;
}
qImDebug() << self << "became first responder";
return YES;
}
- (BOOL)resignFirstResponder
{
qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
// Don't allow activation events of the window that we're doing text on behalf on
// to steal responder.
if (FirstResponderCandidate::currentCandidate() == [self nextResponder]) {
qImDebug() << "not allowing parent window to steal responder";
return NO;
}
if (![super resignFirstResponder])
return NO;
qImDebug() << self << "resigned first responder";
// Dismissing the keyboard will trigger resignFirstResponder, but so will
// a regular responder transfer to another window. In the former case, iOS
// will set the new first-responder to our next-responder, and in the latter
// case we'll have an active responder candidate.
if ([UIResponder currentFirstResponder] == [self nextResponder]) {
// We have resigned the keyboard, and transferred back to the parent view, so unset focus object
Q_ASSERT(!FirstResponderCandidate::currentCandidate());
qImDebug() << "keyboard was closed, clearing focus object";
m_inputContext->clearCurrentFocusObject();
} else {
// We've lost responder status because another window was made active
Q_ASSERT(FirstResponderCandidate::currentCandidate());
}
return YES;
}
- (UIResponder*)nextResponder
{
return qApp->focusWindow() ?
@ -577,7 +627,7 @@
Qt::InputMethodHints imeHints = static_cast<Qt::InputMethodHints>([self imValue:Qt::ImHints].toUInt());
if (!(imeHints & Qt::ImhMultiLine))
m_inputContext->hideVirtualKeyboard();
[self resignFirstResponder];
return;
}

View File

@ -44,6 +44,7 @@
#include "qiosglobal.h"
#include "qiosintegration.h"
#include "qiosviewcontroller.h"
#include "qiostextresponder.h"
#include "qioswindow.h"
#include "qiosmenu.h"
@ -222,6 +223,15 @@
if ([responder isKindOfClass:[QUIView class]])
return NO;
// Nor do we want to deactivate the Qt window if the new responder
// is temporarily handling text input on behalf of a Qt window.
if ([responder isKindOfClass:[QIOSTextInputResponder class]]) {
while ((responder = [responder nextResponder])) {
if ([responder isKindOfClass:[QUIView class]])
return NO;
}
}
return YES;
}