From 4caac57d2ab1bcd2acb524b14757fc68e15580bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pa=CC=84vels=20Nadtoc=CC=8Cajevs?= <7645683+bruvzg@users.noreply.github.com> Date: Tue, 17 Feb 2026 08:25:19 +0200 Subject: [PATCH] [iOS] Add UIScene lifecycle events. (cherry picked from commit b03f0a9e249fea94e145b5761a321a97eb957ce8) --- drivers/apple_embedded/app.swift | 21 ------- drivers/apple_embedded/app_delegate_service.h | 2 +- .../apple_embedded/app_delegate_service.mm | 20 ++++++ drivers/apple_embedded/godot_app_delegate.h | 4 +- drivers/apple_embedded/godot_app_delegate.mm | 63 +++++++++++++++++-- .../godot_apple_embedded-Info.plist | 14 +++++ 6 files changed, 94 insertions(+), 30 deletions(-) diff --git a/drivers/apple_embedded/app.swift b/drivers/apple_embedded/app.swift index c94b5d1ed7..4b03603948 100644 --- a/drivers/apple_embedded/app.swift +++ b/drivers/apple_embedded/app.swift @@ -48,32 +48,11 @@ struct GodotSwiftUIViewController: UIViewControllerRepresentable { @main struct SwiftUIApp: App { @UIApplicationDelegateAdaptor(GDTApplicationDelegate.self) var appDelegate - @Environment(\.scenePhase) private var scenePhase var body: some Scene { WindowGroup { GodotSwiftUIViewController() .ignoresSafeArea() - // UIViewControllerRepresentable does not call viewWillDisappear() nor viewDidDisappear() when - // backgrounding the app, or closing the app's main window, update the renderer here. - .onChange(of: scenePhase) { phase in - // For some reason UIViewControllerRepresentable is not calling viewWillDisappear() - // nor viewDidDisappear when closing the app's main window, call it here so we - // stop the renderer. - switch phase { - case .active: - print("GodotSwiftUIViewController scene active") - GDTAppDelegateService.viewController?.godotView.startRendering() - case .inactive: - print("GodotSwiftUIViewController scene inactive") - GDTAppDelegateService.viewController?.godotView.stopRendering() - case .background: - print("GodotSwiftUIViewController scene backgrounded") - GDTAppDelegateService.viewController?.godotView.stopRendering() - @unknown default: - print("unknown default") - } - } } } } diff --git a/drivers/apple_embedded/app_delegate_service.h b/drivers/apple_embedded/app_delegate_service.h index d2372fdd11..bbf6bd79fc 100644 --- a/drivers/apple_embedded/app_delegate_service.h +++ b/drivers/apple_embedded/app_delegate_service.h @@ -34,7 +34,7 @@ @class GDTViewController; -@interface GDTAppDelegateService : NSObject +@interface GDTAppDelegateService : NSObject @property(strong, class, nonatomic) GDTViewController *viewController; diff --git a/drivers/apple_embedded/app_delegate_service.mm b/drivers/apple_embedded/app_delegate_service.mm index 318731ff92..ee2d03d0ce 100644 --- a/drivers/apple_embedded/app_delegate_service.mm +++ b/drivers/apple_embedded/app_delegate_service.mm @@ -159,6 +159,26 @@ static GDTViewController *mainViewController = nil; // if you open the app list without switching to another app or open/close the // notification panel by swiping from the upper part of the screen. +- (void)sceneDidDisconnect:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) { + OS_AppleEmbedded::get_singleton()->on_focus_out(); +} + +- (void)sceneWillResignActive:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) { + OS_AppleEmbedded::get_singleton()->on_focus_out(); +} + +- (void)sceneDidBecomeActive:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) { + OS_AppleEmbedded::get_singleton()->on_focus_in(); +} + +- (void)sceneDidEnterBackground:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) { + OS_AppleEmbedded::get_singleton()->on_enter_background(); +} + +- (void)sceneWillEnterForeground:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) { + OS_AppleEmbedded::get_singleton()->on_exit_background(); +} + - (void)applicationWillResignActive:(UIApplication *)application { OS_AppleEmbedded::get_singleton()->on_focus_out(); } diff --git a/drivers/apple_embedded/godot_app_delegate.h b/drivers/apple_embedded/godot_app_delegate.h index 7c12871892..09c0b78660 100644 --- a/drivers/apple_embedded/godot_app_delegate.h +++ b/drivers/apple_embedded/godot_app_delegate.h @@ -32,9 +32,9 @@ #import -typedef NSObject GDTAppDelegateServiceProtocol; +typedef NSObject GDTAppDelegateServiceProtocol; -@interface GDTApplicationDelegate : NSObject +@interface GDTApplicationDelegate : NSObject @property(class, readonly, strong) NSArray *services; diff --git a/drivers/apple_embedded/godot_app_delegate.mm b/drivers/apple_embedded/godot_app_delegate.mm index 8d3acbe86e..da658f3655 100644 --- a/drivers/apple_embedded/godot_app_delegate.mm +++ b/drivers/apple_embedded/godot_app_delegate.mm @@ -109,18 +109,69 @@ static NSMutableArray *services = nil; return result; } -/* Can be handled by Info.plist. Not yet supported by Godot. - // MARK: Scene -- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {} +- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) { + UISceneConfiguration *config = [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; + config.delegateClass = [GDTApplicationDelegate class]; + return config; +} -- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions {} - -*/ +- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) { +} // MARK: Life-Cycle +- (void)sceneDidDisconnect:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) { + for (GDTAppDelegateServiceProtocol *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service sceneDidDisconnect:scene]; + } +} + +- (void)sceneDidBecomeActive:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) { + for (GDTAppDelegateServiceProtocol *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service sceneDidBecomeActive:scene]; + } +} + +- (void)sceneWillResignActive:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) { + for (GDTAppDelegateServiceProtocol *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service sceneWillResignActive:scene]; + } +} + +- (void)sceneDidEnterBackground:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) { + for (GDTAppDelegateServiceProtocol *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service sceneDidEnterBackground:scene]; + } +} + +- (void)sceneWillEnterForeground:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) { + for (GDTAppDelegateServiceProtocol *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service sceneWillEnterForeground:scene]; + } +} + // UIApplication lifecycle has become deprecated in favor of UIScene lifecycle GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wdeprecated-declarations") diff --git a/misc/dist/apple_embedded_xcode/godot_apple_embedded/godot_apple_embedded-Info.plist b/misc/dist/apple_embedded_xcode/godot_apple_embedded/godot_apple_embedded-Info.plist index 3d2ae6b52b..782d1448db 100644 --- a/misc/dist/apple_embedded_xcode/godot_apple_embedded/godot_apple_embedded-Info.plist +++ b/misc/dist/apple_embedded_xcode/godot_apple_embedded/godot_apple_embedded-Info.plist @@ -59,5 +59,19 @@ $additional_plist_content $plist_launch_screen_name CADisableMinimumFrameDurationOnPhone + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + + + +