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
Tor Arne Vestbø 2024-04-10 11:45:39 +02:00
parent 409dd9688d
commit 76ebf51bc0
8 changed files with 107 additions and 48 deletions

View File

@ -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

View File

@ -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

View File

@ -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
// -------------------------------------------------------------------------

View File

@ -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;

View File

@ -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"

View File

@ -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;

View File

@ -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

View File

@ -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];
}