1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-04 12:00:25 +00:00

Merge pull request #109974 from rsanchezsaez/apple/swiftui-lifecycle

SwiftUI lifecycle for Apple embedded platforms
This commit is contained in:
Thaddeus Crews
2025-10-03 12:01:09 -05:00
29 changed files with 346 additions and 196 deletions

View File

@@ -1,6 +1,10 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
from SCons.Script import Glob
from platform_methods import setup_swift_builder
Import("env")
env_apple_embedded = env.Clone()
@@ -8,9 +12,21 @@ env_apple_embedded = env.Clone()
# Enable module support
env_apple_embedded.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
# Configure Swift builder
apple_platform = env["APPLE_PLATFORM"]
sdk_path = env["APPLE_SDK_PATH"]
current_path = Dir(".").abspath
bridging_header_filename = "bridging_header_apple_embedded.h"
swift_files = Glob("*.swift")
swift_file_names = list(map(lambda f: f.name, swift_files))
setup_swift_builder(
env_apple_embedded, apple_platform, sdk_path, current_path, bridging_header_filename, swift_file_names
)
# Use bundled Vulkan headers
vulkan_dir = "#thirdparty/vulkan"
env_apple_embedded.Prepend(CPPPATH=[vulkan_dir, vulkan_dir + "/include"])
# Driver source files
env_apple_embedded.add_source_files(env.drivers_sources, "*.mm")
env_apple_embedded.add_source_files(env_apple_embedded.drivers_sources, "*.mm")
env_apple_embedded.add_source_files(env_apple_embedded.drivers_sources, "*.swift")

View File

@@ -0,0 +1,79 @@
/**************************************************************************/
/* app.swift */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
import SwiftUI
import UIKit
struct GodotSwiftUIViewController: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> GDTViewController {
let viewController = GDTViewController()
GDTAppDelegateService.viewController = viewController
return viewController
}
func updateUIViewController(_ uiViewController: GDTViewController, context: Context) {
// NOOP
}
}
@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")
}
}
}
}
}

View File

@@ -36,7 +36,6 @@
@interface GDTAppDelegateService : NSObject <UIApplicationDelegate>
@property(strong, nonatomic) UIWindow *window;
@property(strong, class, readonly, nonatomic) GDTViewController *viewController;
@property(strong, class, nonatomic) GDTViewController *viewController;
@end

View File

@@ -31,8 +31,8 @@
#import "app_delegate_service.h"
#import "godot_view_apple_embedded.h"
#import "godot_view_controller.h"
#import "os_apple_embedded.h"
#import "view_controller.h"
#include "core/config/project_settings.h"
#import "drivers/coreaudio/audio_driver_coreaudio.h"
@@ -41,10 +41,8 @@
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioServices.h>
#define kRenderingFrequency 60
extern int gargc;
extern char **gargv;
int gargc;
char **gargv;
extern int apple_embedded_main(int, char **);
extern void apple_embedded_finish();
@@ -66,18 +64,23 @@ static GDTViewController *mainViewController = nil;
return mainViewController;
}
+ (void)setViewController:(GDTViewController *)viewController {
mainViewController = viewController;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// TODO: might be required to make an early return, so app wouldn't crash because of timeout.
// TODO: logo screen is not displayed while shaders are compiling
// DummyViewController(Splash/LoadingViewController) -> setup -> GodotViewController
#if !defined(VISIONOS_ENABLED)
// Create a full-screen window
CGRect windowBounds = [[UIScreen mainScreen] bounds];
self.window = [[UIWindow alloc] initWithFrame:windowBounds];
#else
self.window = [[UIWindow alloc] init];
#endif
// Fetch the command-line arguments from NSProcessInfo
NSArray *arguments = [[NSProcessInfo processInfo] arguments];
gargc = (int)[arguments count];
gargv = (char **)malloc(sizeof(char *) * gargc);
for (int i = 0; i < gargc; i++) {
NSString *arg = arguments[i];
gargv[i] = strdup([arg UTF8String]);
}
int err = apple_embedded_main(gargc, gargv);
@@ -87,23 +90,12 @@ static GDTViewController *mainViewController = nil;
return NO;
}
GDTViewController *viewController = [[GDTViewController alloc] init];
viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency;
self.window.rootViewController = viewController;
// Show the window
[self.window makeKeyAndVisible];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(onAudioInterruption:)
name:AVAudioSessionInterruptionNotification
object:[AVAudioSession sharedInstance]];
mainViewController = viewController;
int sessionCategorySetting = GLOBAL_GET("audio/general/ios/session_category");
// Initialize with default Ambient category.

View File

@@ -31,7 +31,7 @@
#import "apple_embedded.h"
#import "app_delegate_service.h"
#import "view_controller.h"
#import "godot_view_controller.h"
#import <CoreHaptics/CoreHaptics.h>
#import <UIKit/UIKit.h>

View File

@@ -0,0 +1,36 @@
/**************************************************************************/
/* bridging_header_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#import "app_delegate_service.h"
#import "godot_app_delegate.h"
#import "godot_view_apple_embedded.h"
#import "godot_view_controller.h"

View File

@@ -32,12 +32,12 @@
#import "app_delegate_service.h"
#import "apple_embedded.h"
#import "godot_keyboard_input_view.h"
#import "godot_view_apple_embedded.h"
#import "godot_view_controller.h"
#import "key_mapping_apple_embedded.h"
#import "keyboard_input_view.h"
#import "os_apple_embedded.h"
#import "tts_apple_embedded.h"
#import "view_controller.h"
#include "core/config/project_settings.h"
#include "core/io/file_access_pack.h"
@@ -612,9 +612,8 @@ void DisplayServerAppleEmbedded::window_set_size(const Size2i p_size, WindowID p
}
Size2i DisplayServerAppleEmbedded::window_get_size(WindowID p_window) const {
id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
CGRect windowBounds = appDelegate.window.bounds;
return Size2i(windowBounds.size.width, windowBounds.size.height) * screen_get_max_scale();
CGRect viewBounds = GDTAppDelegateService.viewController.view.bounds;
return Size2i(viewBounds.size.width, viewBounds.size.height) * screen_get_max_scale();
}
Size2i DisplayServerAppleEmbedded::window_get_size_with_decorations(WindowID p_window) const {

View File

@@ -31,6 +31,7 @@
#import "godot_app_delegate.h"
#import "app_delegate_service.h"
#include "core/typedefs.h"
@implementation GDTApplicationDelegate
@@ -120,6 +121,9 @@ static NSMutableArray<GDTAppDelegateServiceProtocol *> *services = nil;
// MARK: Life-Cycle
// UIApplication lifecycle has become deprecated in favor of UIScene lifecycle
GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wdeprecated-declarations")
- (void)applicationDidBecomeActive:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
@@ -461,3 +465,5 @@ static NSMutableArray<GDTAppDelegateServiceProtocol *> *services = nil;
*/
@end
GODOT_CLANG_WARNING_POP

View File

@@ -1,5 +1,5 @@
/**************************************************************************/
/* keyboard_input_view.h */
/* godot_keyboard_input_view.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */

View File

@@ -1,5 +1,5 @@
/**************************************************************************/
/* keyboard_input_view.mm */
/* godot_keyboard_input_view.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,7 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "keyboard_input_view.h"
#import "godot_keyboard_input_view.h"
#import "display_server_apple_embedded.h"
#import "os_apple_embedded.h"

View File

@@ -55,7 +55,7 @@ class String;
@property(strong, readonly, nonatomic) CALayer<GDTDisplayLayer> *renderingLayer;
@property(assign, readonly, nonatomic) BOOL canRender;
@property(assign, nonatomic) NSTimeInterval renderingInterval;
@property(assign, nonatomic) float preferredFrameRate;
// Can be extended by subclasses
- (void)godot_commonInit;
@@ -63,8 +63,8 @@ class String;
// Implemented in subclasses
- (CALayer<GDTDisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName;
- (void)stopRendering;
- (void)startRendering;
- (void)stopRendering;
@end

View File

@@ -60,6 +60,8 @@ static const float earth_gravity = 9.80665;
@property(strong, nonatomic) CMMotionManager *motionManager;
@property(assign, nonatomic) BOOL delegateDidFinishSetUp;
@end
@implementation GDTView
@@ -117,6 +119,9 @@ static const float earth_gravity = 9.80665;
}
- (void)godot_commonInit {
self.preferredFrameRate = 60;
self.useCADisplayLink = bool(GLOBAL_DEF("display.AppleEmbedded/use_cadisplaylink", true)) ? YES : NO;
#if !defined(VISIONOS_ENABLED)
self.contentScaleFactor = [UIScreen mainScreen].scale;
#endif
@@ -197,17 +202,12 @@ static const float earth_gravity = 9.80665;
if (self.useCADisplayLink) {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
if (GLOBAL_GET("display/window/ios/allow_high_refresh_rate")) {
self.displayLink.preferredFramesPerSecond = 120;
} else {
self.displayLink.preferredFramesPerSecond = 60;
}
self.displayLink.preferredFramesPerSecond = self.preferredFrameRate;
// Setup DisplayLink in main thread
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
} else {
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60) target:self selector:@selector(drawView) userInfo:nil repeats:YES];
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / self.preferredFrameRate) target:self selector:@selector(drawView) userInfo:nil repeats:YES];
}
}
@@ -240,10 +240,10 @@ static const float earth_gravity = 9.80665;
return;
}
if (self.delegate) {
BOOL delegateFinishedSetup = [self.delegate godotViewFinishedSetup:self];
if (!delegateFinishedSetup) {
if (self.delegate && !self.delegateDidFinishSetUp) {
[self layoutRenderingLayer]; // Trigger DisplayServerVisionOS::resize_window after Main::start()
self.delegateDidFinishSetUp = [self.delegate godotViewFinishedSetup:self];
if (!_delegateDidFinishSetUp) {
return;
}
}
@@ -262,8 +262,8 @@ static const float earth_gravity = 9.80665;
}
}
- (void)setRenderingInterval:(NSTimeInterval)renderingInterval {
_renderingInterval = renderingInterval;
- (void)setPreferredFrameRate:(float)preferredFrameRate {
_preferredFrameRate = preferredFrameRate;
if (self.canRender) {
[self stopRendering];
@@ -272,6 +272,11 @@ static const float earth_gravity = 9.80665;
}
- (void)layoutSubviews {
[super layoutSubviews];
[self layoutRenderingLayer];
}
- (void)layoutRenderingLayer {
if (self.renderingLayer) {
self.renderingLayer.frame = self.bounds;
[self.renderingLayer layoutDisplayLayer];
@@ -280,8 +285,6 @@ static const float earth_gravity = 9.80665;
DisplayServerAppleEmbedded::get_singleton()->resize_window(self.bounds.size);
}
}
[super layoutSubviews];
}
// MARK: - Input
@@ -415,28 +418,10 @@ static const float earth_gravity = 9.80665;
// our orientation which is not a good thing when you're trying to get
// your user to move the screen in all directions and want consistent
// output
///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
/// is a bit of a hack. Godot obviously knows the orientation so maybe
/// we
// can use that instead? (note that left and right seem swapped)
UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown;
#if !defined(VISIONOS_ENABLED)
#if __IPHONE_OS_VERSION_MAX_ALLOWED < 140000
interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
#if defined(VISIONOS_ENABLED)
UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.effectiveGeometry.interfaceOrientation;
#else
if (@available(iOS 13, *)) {
interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
} else {
interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
#endif
}
#endif
#else
interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
#endif
switch (interfaceOrientation) {

View File

@@ -1,5 +1,5 @@
/**************************************************************************/
/* view_controller.h */
/* godot_view_controller.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */

View File

@@ -1,5 +1,5 @@
/**************************************************************************/
/* view_controller.mm */
/* godot_view_controller.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,13 +28,13 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "view_controller.h"
#import "godot_view_controller.h"
#import "display_server_apple_embedded.h"
#import "godot_keyboard_input_view.h"
#import "godot_view_apple_embedded.h"
#import "godot_view_renderer.h"
#import "key_mapping_apple_embedded.h"
#import "keyboard_input_view.h"
#import "os_apple_embedded.h"
#include "core/config/project_settings.h"
@@ -168,6 +168,16 @@
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.godotView startRendering];
}
- (void)viewDidDisappear:(BOOL)animated {
[self.godotView stopRendering];
[super viewDidDisappear:animated];
}
- (void)observeKeyboard {
print_verbose("Setting up keyboard input view.");
self.keyboardView = [GDTKeyboardInputView new];

View File

@@ -35,7 +35,7 @@
#import "app_delegate_service.h"
#import "display_server_apple_embedded.h"
#import "godot_view_apple_embedded.h"
#import "view_controller.h"
#import "godot_view_controller.h"
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"