iOS: Decouple UIWindow management from QIOSScreen
We now adopt the UIScene lifecycle, where we react to iOS creating UIWindowScenes for connected screens, which we then configure with a single instance of our QUIWindow, that in turn contains the QIOSViewController and QIOSDesktopManagerView that we depend on for our window management. As a result, we can now create and show QWindows on visionOS, which doesn't have UIScreen and hence failed with our old strategy managing our UIWindow via UISScreen. We still do not declare a UIApplicationSceneManifest in our Info.plist, or report UIApplicationSupportsMultipleScenes, as this adds another level to the window management that we're not ready for yet. Task-number: QTBUG-121781 Change-Id: Ic02f43aa6b205289a3f3c8e72c2a6ef575031d9a Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>bb10
parent
409dd9688d
commit
76ebf51bc0
|
|
@ -3,15 +3,21 @@
|
|||
|
||||
#include "qiosapplicationdelegate.h"
|
||||
|
||||
#include "qiosglobal.h"
|
||||
#include "qiosintegration.h"
|
||||
#include "qiosservices.h"
|
||||
#include "qiosviewcontroller.h"
|
||||
#include "qioswindow.h"
|
||||
#include "qiosscreen.h"
|
||||
#include "quiwindow.h"
|
||||
|
||||
#include <qpa/qplatformintegration.h>
|
||||
|
||||
#include <QtCore/QtCore>
|
||||
|
||||
@interface QIOSWindowSceneDelegate : NSObject<UIWindowSceneDelegate>
|
||||
@end
|
||||
|
||||
@implementation QIOSApplicationDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *restorableObjects))restorationHandler
|
||||
|
|
@ -50,5 +56,44 @@
|
|||
return iosServices->handleUrl(QUrl::fromNSURL(url));
|
||||
}
|
||||
|
||||
- (UISceneConfiguration *)application:(UIApplication *)application
|
||||
configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession
|
||||
options:(UISceneConnectionOptions *)options
|
||||
{
|
||||
qCDebug(lcQpaWindowScene) << "Configuring scene for" << connectingSceneSession
|
||||
<< "with options" << options;
|
||||
|
||||
auto *sceneConfig = connectingSceneSession.configuration;
|
||||
sceneConfig.delegateClass = QIOSWindowSceneDelegate.class;
|
||||
return sceneConfig;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation QIOSWindowSceneDelegate
|
||||
|
||||
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions
|
||||
{
|
||||
qCDebug(lcQpaWindowScene) << "Connecting" << scene << "to" << session;
|
||||
|
||||
Q_ASSERT([scene isKindOfClass:UIWindowScene.class]);
|
||||
UIWindowScene *windowScene = static_cast<UIWindowScene*>(scene);
|
||||
|
||||
QUIWindow *window = [[QUIWindow alloc] initWithWindowScene:windowScene];
|
||||
|
||||
QIOSScreen *screen = [&]{
|
||||
for (auto *screen : qGuiApp->screens()) {
|
||||
auto *platformScreen = static_cast<QIOSScreen*>(screen->handle());
|
||||
#if !defined(Q_OS_VISIONOS)
|
||||
if (platformScreen->uiScreen() == windowScene.screen)
|
||||
#endif
|
||||
return platformScreen;
|
||||
}
|
||||
Q_UNREACHABLE();
|
||||
}();
|
||||
|
||||
window.rootViewController = [[[QIOSViewController alloc]
|
||||
initWithWindow:window andScreen:screen] autorelease];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ QT_BEGIN_NAMESPACE
|
|||
Q_DECLARE_LOGGING_CATEGORY(lcQpaApplication);
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaInputMethods);
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaWindow);
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaWindowScene);
|
||||
|
||||
#if !defined(QT_NO_DEBUG)
|
||||
#define qImDebug \
|
||||
|
|
@ -36,7 +37,9 @@ UIDeviceOrientation fromQtScreenOrientation(Qt::ScreenOrientation qtOrientation)
|
|||
int infoPlistValue(NSString* key, int defaultValue);
|
||||
|
||||
class QWindow;
|
||||
class QScreen;
|
||||
UIWindow *presentationWindow(QWindow *);
|
||||
UIView *rootViewForScreen(QScreen *);
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ QT_BEGIN_NAMESPACE
|
|||
Q_LOGGING_CATEGORY(lcQpaApplication, "qt.qpa.application");
|
||||
Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods");
|
||||
Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window");
|
||||
Q_LOGGING_CATEGORY(lcQpaWindowScene, "qt.qpa.window.scene");
|
||||
|
||||
bool isQtApplication()
|
||||
{
|
||||
|
|
@ -109,6 +110,32 @@ UIWindow *presentationWindow(QWindow *window)
|
|||
return uiWindow;
|
||||
}
|
||||
|
||||
UIView *rootViewForScreen(QScreen *screen)
|
||||
{
|
||||
const auto *iosScreen = static_cast<QIOSScreen *>(screen->handle());
|
||||
for (UIScene *scene in [qt_apple_sharedApplication().connectedScenes allObjects]) {
|
||||
if (![scene isKindOfClass:UIWindowScene.class])
|
||||
continue;
|
||||
|
||||
auto *windowScene = static_cast<UIWindowScene*>(scene);
|
||||
|
||||
#if !defined(Q_OS_VISIONOS)
|
||||
if (windowScene.screen != iosScreen->uiScreen())
|
||||
continue;
|
||||
#else
|
||||
Q_UNUSED(iosScreen);
|
||||
#endif
|
||||
|
||||
UIWindow *uiWindow = windowScene.keyWindow;
|
||||
if (!uiWindow && windowScene.windows.count)
|
||||
uiWindow = windowScene.windows[0];
|
||||
|
||||
return uiWindow.rootViewController.view;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ public:
|
|||
#if !defined(Q_OS_VISIONOS)
|
||||
UIScreen *uiScreen() const;
|
||||
#endif
|
||||
UIWindow *uiWindow() const;
|
||||
|
||||
void setUpdatesPaused(bool);
|
||||
|
||||
|
|
@ -55,7 +54,6 @@ private:
|
|||
#if !defined(Q_OS_VISIONOS)
|
||||
UIScreen *m_uiScreen = nullptr;
|
||||
#endif
|
||||
UIWindow *m_uiWindow = nullptr;
|
||||
QRect m_geometry;
|
||||
QRect m_availableGeometry;
|
||||
int m_depth;
|
||||
|
|
|
|||
|
|
@ -176,21 +176,6 @@ QIOSScreen::QIOSScreen(UIScreen *screen)
|
|||
m_physicalDpi = 96;
|
||||
}
|
||||
|
||||
if (!qt_apple_isApplicationExtension()) {
|
||||
for (UIWindow *existingWindow in qt_apple_sharedApplication().windows) {
|
||||
if (existingWindow.screen == m_uiScreen) {
|
||||
m_uiWindow = [existingWindow retain];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_uiWindow) {
|
||||
// Create a window and associated view-controller that we can use
|
||||
m_uiWindow = [[QUIWindow alloc] initWithFrame:[m_uiScreen bounds]];
|
||||
m_uiWindow.rootViewController = [[[QIOSViewController alloc] initWithQIOSScreen:this] autorelease];
|
||||
}
|
||||
}
|
||||
|
||||
m_displayLink = [m_uiScreen displayLinkWithBlock:^(CADisplayLink *) { deliverUpdateRequests(); }];
|
||||
m_displayLink.paused = YES; // Enabled when clients call QWindow::requestUpdate()
|
||||
[m_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
|
||||
|
|
@ -203,8 +188,6 @@ QIOSScreen::QIOSScreen(UIScreen *screen)
|
|||
QIOSScreen::~QIOSScreen()
|
||||
{
|
||||
[m_displayLink invalidate];
|
||||
|
||||
[m_uiWindow release];
|
||||
}
|
||||
|
||||
QString QIOSScreen::name() const
|
||||
|
|
@ -390,7 +373,8 @@ QPixmap QIOSScreen::grabWindow(WId window, int x, int y, int width, int height)
|
|||
if (window && ![reinterpret_cast<id>(window) isKindOfClass:[UIView class]])
|
||||
return QPixmap();
|
||||
|
||||
UIView *view = window ? reinterpret_cast<UIView *>(window) : m_uiWindow.rootViewController.view;
|
||||
UIView *view = window ? reinterpret_cast<UIView *>(window)
|
||||
: rootViewForScreen(screen());
|
||||
|
||||
if (width < 0)
|
||||
width = qMax(view.bounds.size.width - x, CGFloat(0));
|
||||
|
|
@ -424,11 +408,6 @@ UIScreen *QIOSScreen::uiScreen() const
|
|||
}
|
||||
#endif
|
||||
|
||||
UIWindow *QIOSScreen::uiWindow() const
|
||||
{
|
||||
return m_uiWindow;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#include "moc_qiosscreen.cpp"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ QT_END_NAMESPACE
|
|||
|
||||
@interface QIOSViewController : UIViewController
|
||||
|
||||
- (instancetype)initWithQIOSScreen:(QT_PREPEND_NAMESPACE(QIOSScreen) *)screen;
|
||||
- (instancetype)initWithWindow:(UIWindow*)window andScreen:(QT_PREPEND_NAMESPACE(QIOSScreen) *)screen;
|
||||
- (void)updateProperties;
|
||||
- (NSArray*)keyCommands;
|
||||
- (void)handleShortcut:(UIKeyCommand*)keyCommand;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
// -------------------------------------------------------------------------
|
||||
|
||||
@interface QIOSViewController ()
|
||||
@property (nonatomic, assign) UIWindow *window;
|
||||
@property (nonatomic, assign) QPointer<QT_PREPEND_NAMESPACE(QIOSScreen)> platformScreen;
|
||||
@property (nonatomic, assign) BOOL changingOrientation;
|
||||
@end
|
||||
|
|
@ -88,28 +89,25 @@
|
|||
|
||||
- (void)didAddSubview:(UIView *)subview
|
||||
{
|
||||
#if !defined(Q_OS_VISIONOS)
|
||||
Q_UNUSED(subview);
|
||||
|
||||
QT_PREPEND_NAMESPACE(QIOSScreen) *screen = self.qtViewController.platformScreen;
|
||||
|
||||
// The 'window' property of our view is not valid until the window
|
||||
// has been shown, so we have to access it through the QIOSScreen.
|
||||
UIWindow *uiWindow = screen->uiWindow();
|
||||
// Track UIWindow via explicit property on QIOSViewController,
|
||||
// as the window property of our own view is not valid until
|
||||
// the window has been shown (below).
|
||||
UIWindow *uiWindow = self.qtViewController.window;
|
||||
|
||||
if (uiWindow.hidden) {
|
||||
// Associate UIWindow to screen and show it the first time a QWindow
|
||||
// is mapped to the screen. For external screens this means disabling
|
||||
// mirroring mode and presenting alternate content on the screen.
|
||||
uiWindow.screen = screen->uiScreen();
|
||||
// Show the UIWindow the first time a QWindow is mapped to the screen.
|
||||
// For the main screen this hides the launch screen, while for external
|
||||
// screens this disables mirroring of the main screen, so the external
|
||||
// screen can be used for alternate content.
|
||||
uiWindow.hidden = NO;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_VISIONOS)
|
||||
- (void)willRemoveSubview:(UIView *)subview
|
||||
{
|
||||
#if !defined(Q_OS_VISIONOS)
|
||||
Q_UNUSED(subview);
|
||||
|
||||
UIWindow *uiWindow = self.window;
|
||||
|
|
@ -124,11 +122,10 @@
|
|||
// to ensure that we don't try to layout the view that's being removed.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
uiWindow.hidden = YES;
|
||||
uiWindow.screen = [UIScreen mainScreen];
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
|
|
@ -234,9 +231,10 @@
|
|||
@synthesize preferredStatusBarStyle;
|
||||
#endif
|
||||
|
||||
- (instancetype)initWithQIOSScreen:(QT_PREPEND_NAMESPACE(QIOSScreen) *)screen
|
||||
- (instancetype)initWithWindow:(UIWindow*)window andScreen:(QT_PREPEND_NAMESPACE(QIOSScreen) *)screen
|
||||
{
|
||||
if (self = [self init]) {
|
||||
self.window = window;
|
||||
self.platformScreen = screen;
|
||||
|
||||
self.changingOrientation = NO;
|
||||
|
|
@ -297,6 +295,15 @@
|
|||
name:UIApplicationDidChangeStatusBarOrientationNotification
|
||||
object:qt_apple_sharedApplication()];
|
||||
#endif
|
||||
|
||||
// Make sure any top level windows that have already been created
|
||||
// for this screen are reparented into our desktop manager view.
|
||||
for (auto *window : qGuiApp->topLevelWindows()) {
|
||||
if (window->screen()->handle() != self.platformScreen)
|
||||
continue;
|
||||
if (auto *platformWindow = window->handle())
|
||||
platformWindow->setParent(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidUnload
|
||||
|
|
|
|||
|
|
@ -298,14 +298,14 @@ void QIOSWindow::setWindowState(Qt::WindowStates state)
|
|||
|
||||
void QIOSWindow::setParent(const QPlatformWindow *parentWindow)
|
||||
{
|
||||
UIView *parentView = parentWindow ?
|
||||
reinterpret_cast<UIView *>(parentWindow->winId())
|
||||
: isQtApplication() && !isForeignWindow() ?
|
||||
static_cast<QIOSScreen *>(screen())->uiWindow().rootViewController.view
|
||||
: nullptr;
|
||||
UIView *superview = nullptr;
|
||||
if (parentWindow)
|
||||
superview = reinterpret_cast<UIView *>(parentWindow->winId());
|
||||
else if (isQtApplication() && !isForeignWindow())
|
||||
superview = rootViewForScreen(window()->screen());
|
||||
|
||||
if (parentView)
|
||||
[parentView addSubview:m_view];
|
||||
if (superview)
|
||||
[superview addSubview:m_view];
|
||||
else if (quiview_cast(m_view.superview))
|
||||
[m_view removeFromSuperview];
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue