summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms
diff options
context:
space:
mode:
authorRichard Moe Gustavsen <richard.gustavsen@digia.com>2013-11-14 09:58:25 +0100
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-11-25 17:11:26 +0100
commit953d85e049786ddb5666d03b96da57fd546b9368 (patch)
tree9aa6766e3249e36b4b86ba391ece959c55717eed /src/plugins/platforms
parent344a7c540b76c11328fbe2592cf6e9f2b3d942c0 (diff)
iOS: scroll screen when keyboard opens
This change will let QIOSInputContext scroll the root view when the virtual keyboard is open, so that the input cursor is not obscured. Change-Id: If0758f4bf04c2b8e554e0196451154def7e3cb86 Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@digia.com>
Diffstat (limited to 'src/plugins/platforms')
-rw-r--r--src/plugins/platforms/ios/qiosinputcontext.h1
-rw-r--r--src/plugins/platforms/ios/qiosinputcontext.mm123
-rw-r--r--src/plugins/platforms/ios/qioswindow.mm21
3 files changed, 131 insertions, 14 deletions
diff --git a/src/plugins/platforms/ios/qiosinputcontext.h b/src/plugins/platforms/ios/qiosinputcontext.h
index f03b13d002..6ad2a808a6 100644
--- a/src/plugins/platforms/ios/qiosinputcontext.h
+++ b/src/plugins/platforms/ios/qiosinputcontext.h
@@ -62,6 +62,7 @@ public:
bool isInputPanelVisible() const;
void focusWindowChanged(QWindow *focusWindow);
+ void scrollRootView();
private:
QIOSKeyboardListener *m_keyboardListener;
diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm
index ed17dee0c9..7aee1f9bd6 100644
--- a/src/plugins/platforms/ios/qiosinputcontext.mm
+++ b/src/plugins/platforms/ios/qiosinputcontext.mm
@@ -48,7 +48,12 @@
@public
QIOSInputContext *m_context;
BOOL m_keyboardVisible;
+ BOOL m_keyboardVisibleAndDocked;
QRectF m_keyboardRect;
+ QRectF m_keyboardEndRect;
+ NSTimeInterval m_duration;
+ UIViewAnimationCurve m_curve;
+ UIViewController *m_viewController;
}
@end
@@ -60,8 +65,30 @@
if (self) {
m_context = context;
m_keyboardVisible = NO;
- // After the keyboard became undockable (iOS5), UIKeyboardWillShow/UIKeyboardWillHide
- // no longer works for all cases. So listen to keyboard frame changes instead:
+ m_keyboardVisibleAndDocked = NO;
+ m_duration = 0;
+ m_curve = UIViewAnimationCurveEaseOut;
+ m_viewController = 0;
+
+ if (isQtApplication()) {
+ // Get the root view controller that is on the same screen as the keyboard:
+ for (UIWindow *uiWindow in [[UIApplication sharedApplication] windows]) {
+ if (uiWindow.screen == [UIScreen mainScreen]) {
+ m_viewController = [uiWindow.rootViewController retain];
+ break;
+ }
+ }
+ Q_ASSERT(m_viewController);
+ }
+
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(keyboardWillShow:)
+ name:@"UIKeyboardWillShowNotification" object:nil];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(keyboardWillHide:)
+ name:@"UIKeyboardWillHideNotification" object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(keyboardDidChangeFrame:)
@@ -72,25 +99,68 @@
- (void) dealloc
{
+ [m_viewController release];
+ [[NSNotificationCenter defaultCenter]
+ removeObserver:self
+ name:@"UIKeyboardWillShowNotification" object:nil];
+ [[NSNotificationCenter defaultCenter]
+ removeObserver:self
+ name:@"UIKeyboardWillHideNotification" object:nil];
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:@"UIKeyboardDidChangeFrameNotification" object:nil];
[super dealloc];
}
-- (void) keyboardDidChangeFrame:(NSNotification *)notification
+- (QRectF) getKeyboardRect:(NSNotification *)notification
{
- CGRect frame;
- [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&frame];
+ // For Qt applications we rotate the keyboard rect to align with the screen
+ // orientation (which is the interface orientation of the root view controller).
+ // For hybrid apps we follow native behavior, and return the rect unmodified:
+ CGRect keyboardFrame = [[notification userInfo][UIKeyboardFrameEndUserInfoKey] CGRectValue];
+ if (isQtApplication()) {
+ UIView *view = m_viewController.view;
+ return fromCGRect(CGRectOffset([view convertRect:keyboardFrame fromView:view.window], 0, -view.bounds.origin.y));
+ } else {
+ return fromCGRect(keyboardFrame);
+ }
+}
- m_keyboardRect = fromPortraitToPrimary(fromCGRect(frame), QGuiApplication::primaryScreen()->handle());
+- (void) keyboardDidChangeFrame:(NSNotification *)notification
+{
+ m_keyboardRect = [self getKeyboardRect:notification];
m_context->emitKeyboardRectChanged();
- BOOL visible = CGRectIntersectsRect(frame, [UIScreen mainScreen].bounds);
+ BOOL visible = m_keyboardRect.intersects(fromCGRect([UIScreen mainScreen].bounds));
if (m_keyboardVisible != visible) {
m_keyboardVisible = visible;
m_context->emitInputPanelVisibleChanged();
}
+
+ // 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();
+}
+
+- (void) keyboardWillShow:(NSNotification *)notification
+{
+ // Note that UIKeyboardWillShowNotification is only sendt when the keyboard is docked.
+ m_keyboardVisibleAndDocked = YES;
+ m_keyboardEndRect = [self getKeyboardRect:notification];
+ if (!m_duration) {
+ m_duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
+ m_curve = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue] << 16;
+ }
+ m_context->scrollRootView();
+}
+
+- (void) keyboardWillHide:(NSNotification *)notification
+{
+ // Note that UIKeyboardWillHideNotification is also sendt when the keyboard is undocked.
+ m_keyboardVisibleAndDocked = NO;
+ m_keyboardEndRect = [self getKeyboardRect:notification];
+ m_context->scrollRootView();
}
@end
@@ -101,6 +171,8 @@ QIOSInputContext::QIOSInputContext()
, m_focusView(0)
, m_hasPendingHideRequest(false)
{
+ if (isQtApplication())
+ connect(qGuiApp->inputMethod(), &QInputMethod::cursorRectangleChanged, this, &QIOSInputContext::scrollRootView);
connect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &QIOSInputContext::focusWindowChanged);
}
@@ -151,3 +223,40 @@ void QIOSInputContext::focusWindowChanged(QWindow *focusWindow)
[m_focusView release];
m_focusView = [view retain];
}
+
+void QIOSInputContext::scrollRootView()
+{
+ // 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.
+ 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(qGuiApp->focusWindow()->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 (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];
+ }
+}
diff --git a/src/plugins/platforms/ios/qioswindow.mm b/src/plugins/platforms/ios/qioswindow.mm
index 8124d8ffb9..7030df5d32 100644
--- a/src/plugins/platforms/ios/qioswindow.mm
+++ b/src/plugins/platforms/ios/qioswindow.mm
@@ -181,11 +181,14 @@
QRect actualGeometry;
if (m_qioswindow->window()->isTopLevel()) {
UIWindow *uiWindow = self.window;
+ UIView *rootView = uiWindow.rootViewController.view;
CGRect rootViewPositionInRelationToRootViewController =
- [uiWindow.rootViewController.view convertRect:uiWindow.bounds fromView:uiWindow];
+ [rootView convertRect:uiWindow.bounds fromView:uiWindow];
- actualGeometry = fromCGRect(CGRectOffset([self.superview convertRect:self.frame toView:uiWindow.rootViewController.view],
- -rootViewPositionInRelationToRootViewController.origin.x, -rootViewPositionInRelationToRootViewController.origin.y));
+ actualGeometry = fromCGRect(CGRectOffset([self.superview convertRect:self.frame toView:rootView],
+ -rootViewPositionInRelationToRootViewController.origin.x,
+ -rootViewPositionInRelationToRootViewController.origin.y
+ + rootView.bounds.origin.y));
} else {
actualGeometry = fromCGRect(self.frame);
}
@@ -515,13 +518,17 @@ void QIOSWindow::applyGeometry(const QRect &rect)
if (window()->isTopLevel()) {
// The QWindow is in QScreen coordinates, which maps to a possibly rotated root-view-controller.
// Since the root-view-controller might be translated in relation to the UIWindow, we need to
- // check specifically for that and compensate.
+ // check specifically for that and compensate. Also check if the root view has been scrolled
+ // as a result of the keyboard being open.
UIWindow *uiWindow = m_view.window;
+ UIView *rootView = uiWindow.rootViewController.view;
CGRect rootViewPositionInRelationToRootViewController =
- [uiWindow.rootViewController.view convertRect:uiWindow.bounds fromView:uiWindow];
+ [rootView convertRect:uiWindow.bounds fromView:uiWindow];
- m_view.frame = CGRectOffset([m_view.superview convertRect:toCGRect(rect) fromView:m_view.window.rootViewController.view],
- rootViewPositionInRelationToRootViewController.origin.x, rootViewPositionInRelationToRootViewController.origin.y);
+ m_view.frame = CGRectOffset([m_view.superview convertRect:toCGRect(rect) fromView:rootView],
+ rootViewPositionInRelationToRootViewController.origin.x,
+ rootViewPositionInRelationToRootViewController.origin.y
+ + rootView.bounds.origin.y);
} else {
// Easy, in parent's coordinates
m_view.frame = toCGRect(rect);