iOS: Initial attempt at switching to SceneDelegate (UIScene)

Fixes #20884
This commit is contained in:
Henrik Rydgård
2025-10-17 13:43:41 +02:00
parent 5bb76c3572
commit 2f06100b43
9 changed files with 173 additions and 87 deletions
+3 -1
View File
@@ -1340,6 +1340,8 @@ elseif(IOS AND NOT LIBRETRO)
ios/main.mm
ios/AppDelegate.mm
ios/AppDelegate.h
ios/SceneDelegate.mm
ios/SceneDelegate.h
ios/DisplayManager.h
ios/DisplayManager.mm
ios/Controls.h
@@ -2964,7 +2966,7 @@ endif()
# packaging and code signing
if(IOS AND NOT LIBRETRO)
if(IOS_APP_STORE)
set(DEPLOYMENT_TARGET 12.0)
set(DEPLOYMENT_TARGET 13.0)
else()
set(DEPLOYMENT_TARGET 11.0)
endif()
+1 -1
View File
@@ -1531,7 +1531,7 @@ void LaunchBuyGold(ScreenManager *screenManager) {
screenManager->push(new IAPScreen());
} else {
#if PPSSPP_PLATFORM(IOS_APP_STORE)
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://apps.apple.com/us/app/ppsspp-gold-psp-emulator/id6502287918");
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.ppsspp.org/buygold_ios");
#elif PPSSPP_PLATFORM(ANDROID)
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "market://details?id=org.ppsspp.ppssppgold");
#else
+1 -1
View File
@@ -12,7 +12,7 @@
# PPSSPP platform flags
set(MOBILE_DEVICE ON)
set(USING_GLES2 ON)
set(IPHONEOS_DEPLOYMENT_TARGET 12.0)
set(IPHONEOS_DEPLOYMENT_TARGET 13.0)
add_definitions(
-DGL_ETC1_RGB8_OES=0
-U__STRICT_ANSI__
+1 -2
View File
@@ -8,8 +8,7 @@
@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UIScreen *screen;
@property (strong, nonatomic) id<PPSSPPViewController> viewController;
@property (nonatomic, strong) NSDictionary *launchOptions;
- (void)restart:(const char *)args;
- (BOOL)launchPPSSPP:(int)argc argv:(char**)argv;
+16 -82
View File
@@ -1,7 +1,5 @@
#import "AppDelegate.h"
#import "ViewControllerCommon.h"
#import "ViewController.h"
#import "ViewControllerMetal.h"
#import "SceneDelegate.h"
#import "iOSCoreAudio.h"
#import "Common/System/System.h"
#import "Common/System/NativeApp.h"
@@ -11,6 +9,12 @@
#import "IAPManager.h"
#import <AVFoundation/AVFoundation.h>
#import <objc/runtime.h>
// TODO: Unfortunate hack to force link SceneDelegate class.
// This is necessary because otherwise classes from static libraries aren't loaded.
// We should move all the iOS code into the main binary directly to avoid this problem.
__attribute__((used)) static Class _forceLinkSceneDelegate = [SceneDelegate class];
@implementation AppDelegate
@@ -77,49 +81,21 @@
}
-(BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
int argc = 1;
char *argv[5]{};
NSURL *nsUrl = [launchOptions objectForKey:UIApplicationLaunchOptionsURLKey];
if (nsUrl != nullptr && nsUrl.isFileURL) {
NSString *nsString = nsUrl.path;
const char *string = nsString.UTF8String;
argv[argc++] = (char*)string;
self.launchOptions = launchOptions;
// Make sure SceneDelegate class is loaded
Class cls = objc_getClass("SceneDelegate");
if (!cls) {
NSLog(@"⚠️ SceneDelegate not found via objc_getClass");
} else {
NSLog(@"✅ SceneDelegate loaded via objc_getClass");
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioSessionInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMediaServicesWereReset:) name:AVAudioSessionMediaServicesWereResetNotification object:nil];
#if PPSSPP_PLATFORM(IOS_APP_STORE)
[IAPManager sharedIAPManager]; // Kick off the IAPManager early.
#endif // IOS_APP_STORE
return [self launchPPSSPP:argc argv:argv];
}
- (BOOL)launchPPSSPP:(int)argc argv:(char**)argv;
{
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *bundlePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/assets/"];
NativeInit(argc, (const char**)argv, documentsPath.UTF8String, bundlePath.UTF8String, NULL);
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Choose viewcontroller depending on backend.
if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {
PPSSPPViewControllerMetal *vc = [[PPSSPPViewControllerMetal alloc] init];
self.viewController = vc;
self.window.rootViewController = vc;
} else {
PPSSPPViewControllerGL *vc = [[PPSSPPViewControllerGL alloc] init];
// Here we can switch viewcontroller depending on backend.
self.viewController = vc;
self.window.rootViewController = vc;
}
[self.window makeKeyAndVisible];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioSessionInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMediaServicesWereReset:) name:AVAudioSessionMediaServicesWereResetNotification object:nil];
return YES;
}
@@ -128,49 +104,7 @@
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)restart:(const char*)restartArgs {
INFO_LOG(Log::G3D, "Restart requested: %s", restartArgs);
[self.viewController willResignActive];
[self.viewController shutdown];
self.window.rootViewController = nil;
self.viewController = nil;
// App was requested to restart, probably.
INFO_LOG(Log::G3D, "viewController nilled");
NativeShutdown();
// TODO: Ignoring the command line for now.
// Hoping that overwriting the viewController works as expected...
[self launchPPSSPP:0 argv:nullptr];
[self.viewController didBecomeActive];
}
- (void)applicationWillResignActive:(UIApplication *)application {
INFO_LOG(Log::G3D, "willResignActive");
[self.viewController willResignActive];
if (g_Config.bEnableSound) {
iOSCoreAudioShutdown();
}
System_PostUIMessage(UIMessage::LOST_FOCUS);
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
INFO_LOG(Log::G3D, "didBecomeActive");
if (g_Config.bEnableSound) {
iOSCoreAudioInit();
}
System_PostUIMessage(UIMessage::GOT_FOCUS);
[self.viewController didBecomeActive];
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Seems like a bad idea.
// exit(0);
}
@end
+8
View File
@@ -215,4 +215,12 @@ static bool SafeStringEqual(NSString *a, NSString *b) {
}
}
- (void)productsRequest:(nonnull SKProductsRequest *)request didReceiveResponse:(nonnull SKProductsResponse *)response {
NSLog(@"Ignoring [productsRequest]");
}
- (void)paymentQueue:(nonnull SKPaymentQueue *)queue updatedTransactions:(nonnull NSArray<SKPaymentTransaction *> *)transactions {
NSLog(@"Ignoring [paymentQueue]");
}
@end
+15
View File
@@ -206,5 +206,20 @@
</dict>
</dict>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
</dict>
</plist>
+9
View File
@@ -0,0 +1,9 @@
// SceneDelegate.h
#import <UIKit/UIKit.h>
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
@property (strong, nonatomic) UIWindow * window;
@property (strong, nonatomic) UIViewController *viewController;
@end
+119
View File
@@ -0,0 +1,119 @@
// SceneDelegate.mm
#import "SceneDelegate.h"
#import "AppDelegate.h"
#import "iOSCoreAudio.h"
#import "ViewControllerCommon.h"
#import "ViewController.h"
#import "ViewControllerMetal.h"
#import "Common/System/NativeApp.h"
#import "Common/System/System.h"
#import "Core/System.h"
#import "Core/Config.h"
#import "Common/Log.h"
#import "IAPManager.h"
#import <AVFoundation/AVFoundation.h>
@implementation SceneDelegate
+ (void)load {
NSLog(@"✅ SceneDelegate class was loaded!");
}
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
if (![scene isKindOfClass:[UIWindowScene class]]) {
return;
}
UIWindowScene *windowScene = (UIWindowScene *)scene;
if (self.window) {
NSLog(@"✅ Window already exists, not creating again!");
return;
}
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
NSDictionary *launchOptions = appDelegate.launchOptions;
int argc = 1;
char *argv[5]{};
NSURL *nsUrl = [launchOptions objectForKey:UIApplicationLaunchOptionsURLKey];
if (nsUrl != nullptr && nsUrl.isFileURL) {
NSString *nsString = nsUrl.path;
const char *string = nsString.UTF8String;
argv[argc++] = (char*)string;
}
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *bundlePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/assets/"];
NativeInit(argc, (const char**)argv, documentsPath.UTF8String, bundlePath.UTF8String, NULL);
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
// self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Choose viewcontroller depending on backend.
if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {
PPSSPPViewControllerMetal *vc = [[PPSSPPViewControllerMetal alloc] init];
self.viewController = vc;
self.window.rootViewController = vc;
} else {
PPSSPPViewControllerGL *vc = [[PPSSPPViewControllerGL alloc] init];
// Here we can switch viewcontroller depending on backend.
self.viewController = vc;
self.window.rootViewController = vc;
}
[self.window makeKeyAndVisible];
}
- (void)restart:(const char*)restartArgs {
INFO_LOG(Log::G3D, "SceneDelegate: Restart requested: %s", restartArgs);
// Notify current view controller
[self.viewController willResignActive];
[self.viewController shutdown];
// Remove the current root view controller
self.window.rootViewController = nil;
self.viewController = nil;
INFO_LOG(Log::G3D, "SceneDelegate: viewController nilled");
// Shut down native systems
NativeShutdown();
// Launch a fresh instance
[self launchPPSSPP:0 argv:nullptr];
// Notify new view controller
[self.viewController didBecomeActive];
}
- (void)sceneWillResignActive:(UIScene *)scene {
INFO_LOG(Log::G3D, "sceneWillResignActive");
[self.viewController willResignActive];
if (g_Config.bEnableSound) {
iOSCoreAudioShutdown();
}
System_PostUIMessage(UIMessage::LOST_FOCUS);
}
- (void)sceneDidBecomeActive:(UIScene *)scene {
INFO_LOG(Log::G3D, "sceneDidBecomeActive");
if (g_Config.bEnableSound) {
iOSCoreAudioInit();
}
System_PostUIMessage(UIMessage::GOT_FOCUS);
[self.viewController didBecomeActive];
}
@end