diff options
Diffstat (limited to 'src/plugins/platforms/ios')
-rw-r--r-- | src/plugins/platforms/ios/qiosinputcontext.mm | 19 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiosscreen.mm | 17 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiostextresponder.h | 1 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiostextresponder.mm | 11 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiosviewcontroller.h | 14 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiosviewcontroller.mm | 117 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qioswindow.mm | 19 |
7 files changed, 166 insertions, 32 deletions
diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm index 76c02d939f..6e56f47954 100644 --- a/src/plugins/platforms/ios/qiosinputcontext.mm +++ b/src/plugins/platforms/ios/qiosinputcontext.mm @@ -75,9 +75,7 @@ static QUIView *focusView() - (id)initWithQIOSInputContext:(QIOSInputContext *)context { - id originalSelf = self; if (self = [super initWithTarget:self action:@selector(gestureStateChanged:)]) { - Q_ASSERT(self == originalSelf); m_context = context; @@ -505,7 +503,22 @@ void QIOSInputContext::scroll(int y) [rootView.layer addAnimation:animation forKey:@"AnimateSubLayerTransform"]; rootView.layer.sublayerTransform = translationTransform; - [rootView.qtViewController updateProperties]; + bool keyboardScrollIsActive = y != 0; + + // Raise all known windows to above the status-bar if we're scrolling the screen, + // while keeping the relative window level between the windows the same. + NSArray *applicationWindows = [[UIApplication sharedApplication] windows]; + static QHash<UIWindow *, UIWindowLevel> originalWindowLevels; + for (UIWindow *window in applicationWindows) { + if (keyboardScrollIsActive && !originalWindowLevels.contains(window)) + originalWindowLevels.insert(window, window.windowLevel); + + UIWindowLevel windowLevelAdjustment = keyboardScrollIsActive ? UIWindowLevelStatusBar : 0; + window.windowLevel = originalWindowLevels.value(window) + windowLevelAdjustment; + + if (!keyboardScrollIsActive) + originalWindowLevels.remove(window); + } } completion:^(BOOL){ if (self) { diff --git a/src/plugins/platforms/ios/qiosscreen.mm b/src/plugins/platforms/ios/qiosscreen.mm index 4af2a4965f..712bf0098b 100644 --- a/src/plugins/platforms/ios/qiosscreen.mm +++ b/src/plugins/platforms/ios/qiosscreen.mm @@ -46,6 +46,7 @@ #include <qpa/qwindowsysteminterface.h> #include "qiosapplicationdelegate.h" #include "qiosviewcontroller.h" +#include "quiview.h" #include <sys/sysctl.h> @@ -244,6 +245,22 @@ void QIOSScreen::updateProperties() m_geometry = fromCGRect([rootView convertRect:m_uiScreen.bounds fromView:m_uiWindow]).toRect(); m_availableGeometry = fromCGRect([rootView convertRect:m_uiScreen.applicationFrame fromView:m_uiWindow]).toRect(); + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_IOS_8_0 && ![m_uiWindow.rootViewController shouldAutorotate]) { + // Setting the statusbar orientation (content orientation) on iOS8+ will result in the UIScreen + // updating its geometry and available geometry, which in the case of content orientation is not + // what we want. We want to reflect the screen geometry based on the locked orientation, and + // adjust the available geometry based on the repositioned status bar for the current status + // bar orientation. + + Qt::ScreenOrientation lockedOrientation = toQtScreenOrientation(UIDeviceOrientation(rootView.qtViewController.lockedOrientation)); + Qt::ScreenOrientation contenOrientation = toQtScreenOrientation(UIDeviceOrientation([UIApplication sharedApplication].statusBarOrientation)); + + QTransform transform = screen()->transformBetween(lockedOrientation, contenOrientation, m_geometry).inverted(); + + m_geometry = transform.mapRect(m_geometry); + m_availableGeometry = transform.mapRect(m_availableGeometry); + } + if (m_geometry != previousGeometry || m_availableGeometry != previousAvailableGeometry) { const qreal millimetersPerInch = 25.4; m_physicalSize = QSizeF(m_geometry.size()) / m_unscaledDpi * millimetersPerInch; diff --git a/src/plugins/platforms/ios/qiostextresponder.h b/src/plugins/platforms/ios/qiostextresponder.h index 118ab8958a..21b61bf8da 100644 --- a/src/plugins/platforms/ios/qiostextresponder.h +++ b/src/plugins/platforms/ios/qiostextresponder.h @@ -51,6 +51,7 @@ class QIOSInputContext; QIOSInputContext *m_inputContext; QString m_markedText; BOOL m_inSendEventToFocusObject; + BOOL m_inSelectionChange; } - (id)initWithInputContext:(QIOSInputContext *)context; diff --git a/src/plugins/platforms/ios/qiostextresponder.mm b/src/plugins/platforms/ios/qiostextresponder.mm index bebc7577f8..15fade0838 100644 --- a/src/plugins/platforms/ios/qiostextresponder.mm +++ b/src/plugins/platforms/ios/qiostextresponder.mm @@ -171,6 +171,7 @@ return self; m_inSendEventToFocusObject = NO; + m_inSelectionChange = NO; m_inputContext = inputContext; QVariantMap platformData = [self imValue:Qt::ImPlatformData].toMap(); @@ -302,6 +303,7 @@ return; if (updatedProperties & (Qt::ImCursorPosition | Qt::ImAnchorPosition)) { + QScopedValueRollback<BOOL> rollback(m_inSelectionChange, true); [self.inputDelegate selectionWillChange:self]; [self.inputDelegate selectionDidChange:self]; } @@ -349,6 +351,15 @@ - (void)setSelectedTextRange:(UITextRange *)range { + if (m_inSelectionChange) { + // After [UITextInputDelegate selectionWillChange], UIKit will cancel + // any ongoing auto correction (if enabled) and ask us to set an empty selection. + // This is contradictory to our current attempt to set a selection, so we ignore + // the callback. UIKit will be re-notified of the new selection after + // [UITextInputDelegate selectionDidChange]. + return; + } + QUITextRange *r = static_cast<QUITextRange *>(range); QList<QInputMethodEvent::Attribute> attrs; attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, r.range.location, r.range.length, 0); diff --git a/src/plugins/platforms/ios/qiosviewcontroller.h b/src/plugins/platforms/ios/qiosviewcontroller.h index 586edd589d..df7ce0ff4a 100644 --- a/src/plugins/platforms/ios/qiosviewcontroller.h +++ b/src/plugins/platforms/ios/qiosviewcontroller.h @@ -35,15 +35,17 @@ class QIOSScreen; -@interface QIOSViewController : UIViewController { - QIOSScreen *m_screen; -} +@interface QIOSViewController : UIViewController -@property (nonatomic, assign) BOOL changingOrientation; +- (id)initWithQIOSScreen:(QIOSScreen *)screen; +- (void)updateProperties; + +@property (nonatomic, assign) UIInterfaceOrientation lockedOrientation; + +// UIViewController +@property (nonatomic, assign) BOOL shouldAutorotate; @property (nonatomic, assign) BOOL prefersStatusBarHidden; @property (nonatomic, assign) UIStatusBarAnimation preferredStatusBarUpdateAnimation; -- (id)initWithQIOSScreen:(QIOSScreen *)screen; -- (void)updateProperties; @end diff --git a/src/plugins/platforms/ios/qiosviewcontroller.mm b/src/plugins/platforms/ios/qiosviewcontroller.mm index a2d81e3b6c..01bc84ae68 100644 --- a/src/plugins/platforms/ios/qiosviewcontroller.mm +++ b/src/plugins/platforms/ios/qiosviewcontroller.mm @@ -41,6 +41,8 @@ #import "qiosviewcontroller.h" +#include <QtCore/qscopedvaluerollback.h> + #include <QtGui/QGuiApplication> #include <QtGui/QWindow> #include <QtGui/QScreen> @@ -119,6 +121,13 @@ // ------------------------------------------------------------------------- +@interface QIOSViewController () { + QIOSScreen *m_screen; + BOOL m_updatingProperties; +} +@property (nonatomic, assign) BOOL changingOrientation; +@end + @implementation QIOSViewController - (id)initWithQIOSScreen:(QIOSScreen *)screen @@ -147,6 +156,7 @@ #endif self.changingOrientation = NO; + self.shouldAutorotate = [super shouldAutorotate]; // Status bar may be initially hidden at startup through Info.plist self.prefersStatusBarHidden = infoPlistValue(@"UIStatusBarHidden", false); @@ -173,6 +183,10 @@ [center addObserver:self selector:@selector(willChangeStatusBarFrame:) name:UIApplicationWillChangeStatusBarFrameNotification object:[UIApplication sharedApplication]]; + + [center addObserver:self selector:@selector(didChangeStatusBarOrientation:) + name:UIApplicationDidChangeStatusBarOrientationNotification + object:[UIApplication sharedApplication]]; } - (void)viewDidUnload @@ -183,19 +197,16 @@ // ------------------------------------------------------------------------- --(BOOL)shouldAutorotate -{ - // Until a proper orientation and rotation API is in place, we always auto rotate. - // If auto rotation is not wanted, you would need to switch it off manually from Info.plist. - return YES; -} - #if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_6_0) -(NSUInteger)supportedInterfaceOrientations { - // We need to tell iOS that we support all orientations in order to set - // status bar orientation when application content orientation changes. - return UIInterfaceOrientationMaskAll; + // As documented by Apple in the iOS 6.0 release notes, setStatusBarOrientation:animated: + // only works if the supportedInterfaceOrientations of the view controller is 0, making + // us responsible for ensuring that the status bar orientation is consistent. We enter + // this mode when auto-rotation is disabled due to an explicit content orientation being + // set on the focus window. Note that this is counter to what the documentation for + // supportedInterfaceOrientations says, which states that the method should not return 0. + return [self shouldAutorotate] ? UIInterfaceOrientationMaskAll : 0; } #endif @@ -203,7 +214,7 @@ -(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { Q_UNUSED(interfaceOrientation); - return YES; + return [self shouldAutorotate]; } #endif @@ -250,6 +261,22 @@ }]; } +- (void)didChangeStatusBarOrientation:(NSNotification *)notification +{ + Q_UNUSED(notification); + + if (self.view.window.screen != [UIScreen mainScreen]) + return; + + // If the statusbar changes orientation due to auto-rotation we don't care, + // there will be re-layout anyways. Only if the statusbar changes due to + // reportContentOrientation, we need to update the window layout. + if (self.changingOrientation) + return; + + [self.view setNeedsLayout]; +} + - (void)viewWillLayoutSubviews { if (!QCoreApplication::instance()) @@ -265,6 +292,15 @@ if (!isQtApplication()) return; + // Prevent recursion caused by updating the status bar appearance (position + // or visibility), which in turn may cause a layout of our subviews, and + // a reset of window-states, which themselves affect the view controller + // properties such as the statusbar visibilty. + if (m_updatingProperties) + return; + + QScopedValueRollback<BOOL> updateRollback(m_updatingProperties, YES); + QWindow *focusWindow = QGuiApplication::focusWindow(); // If we don't have a focus window we leave the statusbar @@ -281,14 +317,10 @@ // All decisions are based on the the top level window focusWindow = qt_window_private(focusWindow)->topLevelWindow(); - bool hasScrolledRootViewDueToVirtualKeyboard = - !CATransform3DIsIdentity(self.view.layer.sublayerTransform); + UIApplication *uiApplication = [UIApplication sharedApplication]; bool currentStatusBarVisibility = self.prefersStatusBarHidden; - self.prefersStatusBarHidden = focusWindow->windowState() == Qt::WindowFullScreen - || hasScrolledRootViewDueToVirtualKeyboard; - self.preferredStatusBarUpdateAnimation = hasScrolledRootViewDueToVirtualKeyboard ? - UIStatusBarAnimationFade : UIStatusBarAnimationNone; + self.prefersStatusBarHidden = focusWindow->windowState() == Qt::WindowFullScreen; if (self.prefersStatusBarHidden != currentStatusBarVisibility) { #if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_7_0) @@ -297,13 +329,60 @@ } else #endif { - [[UIApplication sharedApplication] - setStatusBarHidden:self.prefersStatusBarHidden + [uiApplication setStatusBarHidden:self.prefersStatusBarHidden withAnimation:self.preferredStatusBarUpdateAnimation]; } [self.view setNeedsLayout]; } + + + // -------------- Content orientation --------------- + + static BOOL kAnimateContentOrientationChanges = YES; + + Qt::ScreenOrientation contentOrientation = focusWindow->contentOrientation(); + if (contentOrientation != Qt::PrimaryOrientation) { + // An explicit content orientation has been reported for the focus window, + // so we keep the status bar in sync with content orientation. This will ensure + // that the task bar (and associated gestures) are also rotated accordingly. + + if (self.shouldAutorotate) { + // We are moving from Qt::PrimaryOrientation to an explicit orientation, + // so we need to store the current statusbar orientation, as we need it + // later when mapping screen coordinates for QScreen and for returning + // to Qt::PrimaryOrientation. + self.lockedOrientation = uiApplication.statusBarOrientation; + + // Calling setStatusBarOrientation only has an effect when auto-rotation is + // disabled, which makes sense when there's an explicit content orientation. + self.shouldAutorotate = NO; + } + + [uiApplication setStatusBarOrientation: + UIInterfaceOrientation(fromQtScreenOrientation(contentOrientation)) + animated:kAnimateContentOrientationChanges]; + + } else { + // The content orientation is set to Qt::PrimaryOrientation, meaning + // that auto-rotation should be enabled. But we may be coming out of + // a state of locked orientation, which needs some cleanup before we + // can enable auto-rotation again. + if (!self.shouldAutorotate) { + // First we need to restore the statusbar to what it was at the + // time of locking the orientation, otherwise iOS will be very + // confused when it starts doing auto-rotation again. + [uiApplication setStatusBarOrientation: + UIInterfaceOrientation(self.lockedOrientation) + animated:kAnimateContentOrientationChanges]; + + // Then we can re-enable auto-rotation + self.shouldAutorotate = YES; + + // And finally let iOS rotate the root view to match the device orientation + [UIViewController attemptRotationToDeviceOrientation]; + } + } } #if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_7_0) diff --git a/src/plugins/platforms/ios/qioswindow.mm b/src/plugins/platforms/ios/qioswindow.mm index 480062e4de..6c4614408d 100644 --- a/src/plugins/platforms/ios/qioswindow.mm +++ b/src/plugins/platforms/ios/qioswindow.mm @@ -75,6 +75,15 @@ QIOSWindow::QIOSWindow(QWindow *window) setWindowState(window->windowState()); setOpacity(window->opacity()); + + Qt::ScreenOrientation initialOrientation = window->contentOrientation(); + if (initialOrientation != Qt::PrimaryOrientation) { + // Start up in portrait, then apply possible content orientation, + // as per Apple's documentation. + dispatch_async(dispatch_get_main_queue(), ^{ + handleContentOrientationChange(initialOrientation); + }); + } } QIOSWindow::~QIOSWindow() @@ -322,10 +331,12 @@ void QIOSWindow::updateWindowLevel() void QIOSWindow::handleContentOrientationChange(Qt::ScreenOrientation orientation) { - // Keep the status bar in sync with content orientation. This will ensure - // that the task bar (and associated gestures) are aligned correctly: - UIInterfaceOrientation uiOrientation = UIInterfaceOrientation(fromQtScreenOrientation(orientation)); - [[UIApplication sharedApplication] setStatusBarOrientation:uiOrientation animated:NO]; + // Update the QWindow representation straight away, so that + // we can update the statusbar orientation based on the new + // content orientation. + qt_window_private(window())->contentOrientation = orientation; + + [m_view.qtViewController updateProperties]; } void QIOSWindow::applicationStateChanged(Qt::ApplicationState) |