Skip to content

Commit b46e26e

Browse files
committed
Added support for the UIScene life cycle on Apple platforms
Fixes #12680
1 parent b6f67dd commit b46e26e

File tree

4 files changed

+235
-7
lines changed

4 files changed

+235
-7
lines changed

include/SDL3/SDL_video.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ typedef Uint32 SDL_WindowID;
9797
* uninitialized will either return the user provided value, if one was set
9898
* prior to initialization, or NULL. See docs/README-wayland.md for more
9999
* information.
100+
*
101+
* \since This macro is available since SDL 3.2.0.
100102
*/
101103
#define SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER "SDL.video.wayland.wl_display"
102104

src/video/uikit/SDL_uikitappdelegate.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@
2929

3030
@end
3131

32+
API_AVAILABLE(ios(13.0))
33+
@interface SDLUIKitSceneDelegate : NSObject <UIApplicationDelegate, UIWindowSceneDelegate>
34+
35+
+ (NSString *)getSceneDelegateClassName;
36+
37+
- (void)hideLaunchScreen;
38+
39+
@end
40+
3241
@interface SDLUIKitDelegate : NSObject <UIApplicationDelegate>
3342

3443
+ (id)sharedAppDelegate;

src/video/uikit/SDL_uikitappdelegate.m

Lines changed: 175 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,15 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void *reserve
5959

6060
// Give over control to run loop, SDLUIKitDelegate will handle most things from here
6161
@autoreleasepool {
62-
UIApplicationMain(argc, argv, nil, [SDLUIKitDelegate getAppDelegateClassName]);
62+
NSString *name = nil;
63+
64+
if (@available(iOS 13.0, tvOS 13.0, *)) {
65+
name = [SDLUIKitSceneDelegate getSceneDelegateClassName];
66+
}
67+
if (!name) {
68+
name = [SDLUIKitDelegate getAppDelegateClassName];
69+
}
70+
UIApplicationMain(argc, argv, nil, name);
6371
}
6472

6573
// free the memory we used to hold copies of argc and argv
@@ -162,6 +170,7 @@ - (UIStatusBarStyle)preferredStatusBarStyle
162170
@end
163171
#endif // !SDL_PLATFORM_TVOS
164172

173+
165174
@interface SDLLaunchScreenController ()
166175

167176
#ifndef SDL_PLATFORM_TVOS
@@ -343,7 +352,170 @@ - (NSUInteger)supportedInterfaceOrientations
343352
}
344353
#endif // !SDL_PLATFORM_TVOS
345354

346-
@end
355+
@end // SDLLaunchScreenController
356+
357+
358+
API_AVAILABLE(ios(13.0))
359+
@implementation SDLUIKitSceneDelegate
360+
{
361+
UIWindow *launchWindow;
362+
}
363+
364+
+ (NSString *)getSceneDelegateClassName
365+
{
366+
return @"SDLUIKitSceneDelegate";
367+
}
368+
369+
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions
370+
{
371+
if (![scene isKindOfClass:[UIWindowScene class]]) {
372+
return;
373+
}
374+
375+
UIWindowScene *windowScene = (UIWindowScene *)scene;
376+
windowScene.delegate = self;
377+
378+
NSBundle *bundle = [NSBundle mainBundle];
379+
380+
#ifdef SDL_IPHONE_LAUNCHSCREEN
381+
UIViewController *vc = nil;
382+
NSString *screenname = nil;
383+
384+
#if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS)
385+
screenname = [bundle objectForInfoDictionaryKey:@"UILaunchStoryboardName"];
386+
387+
if (screenname) {
388+
@try {
389+
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:screenname bundle:bundle];
390+
__auto_type storyboardVc = [storyboard instantiateInitialViewController];
391+
vc = [[SDLLaunchStoryboardViewController alloc] initWithStoryboardViewController:storyboardVc];
392+
}
393+
@catch (NSException *exception) {
394+
// Do nothing (there's more code to execute below).
395+
}
396+
}
397+
#endif
398+
399+
if (vc == nil) {
400+
vc = [[SDLLaunchScreenController alloc] initWithNibName:screenname bundle:bundle];
401+
}
402+
403+
if (vc.view) {
404+
#ifdef SDL_PLATFORM_VISIONOS
405+
CGRect viewFrame = CGRectMake(0, 0, SDL_XR_SCREENWIDTH, SDL_XR_SCREENHEIGHT);
406+
#else
407+
CGRect viewFrame = windowScene.coordinateSpace.bounds;
408+
#endif
409+
launchWindow = [[UIWindow alloc] initWithWindowScene:windowScene];
410+
launchWindow.frame = viewFrame;
411+
412+
launchWindow.windowLevel = UIWindowLevelNormal + 1.0;
413+
launchWindow.hidden = NO;
414+
launchWindow.rootViewController = vc;
415+
}
416+
#endif
417+
418+
// Set working directory to resource path
419+
[[NSFileManager defaultManager] changeCurrentDirectoryPath:[bundle resourcePath]];
420+
421+
// Handle any connection options (like opening URLs)
422+
for (NSUserActivity *activity in connectionOptions.userActivities) {
423+
if (activity.webpageURL) {
424+
[self handleURL:activity.webpageURL];
425+
}
426+
}
427+
428+
for (UIOpenURLContext *urlContext in connectionOptions.URLContexts) {
429+
[self handleURL:urlContext.URL];
430+
}
431+
432+
SDL_SetMainReady();
433+
[self performSelector:@selector(postFinishLaunch) withObject:nil afterDelay:0.0];
434+
}
435+
436+
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts
437+
{
438+
for (UIOpenURLContext *context in URLContexts) {
439+
[self handleURL:context.URL];
440+
}
441+
}
442+
443+
- (void)sceneDidBecomeActive:(UIScene *)scene
444+
{
445+
SDL_OnApplicationDidEnterForeground();
446+
}
447+
448+
- (void)sceneWillResignActive:(UIScene *)scene
449+
{
450+
SDL_OnApplicationWillEnterBackground();
451+
}
452+
453+
- (void)sceneWillEnterForeground:(UIScene *)scene
454+
{
455+
SDL_OnApplicationWillEnterForeground();
456+
}
457+
458+
- (void)sceneDidEnterBackground:(UIScene *)scene
459+
{
460+
SDL_OnApplicationDidEnterBackground();
461+
}
462+
463+
- (void)handleURL:(NSURL *)url
464+
{
465+
const char *sourceApplicationCString = NULL;
466+
NSURL *fileURL = url.filePathURL;
467+
if (fileURL != nil) {
468+
SDL_SendDropFile(NULL, sourceApplicationCString, fileURL.path.UTF8String);
469+
} else {
470+
SDL_SendDropFile(NULL, sourceApplicationCString, url.absoluteString.UTF8String);
471+
}
472+
SDL_SendDropComplete(NULL);
473+
}
474+
475+
- (void)hideLaunchScreen
476+
{
477+
UIWindow *window = launchWindow;
478+
479+
if (!window || window.hidden) {
480+
return;
481+
}
482+
483+
launchWindow = nil;
484+
485+
[UIView animateWithDuration:0.2
486+
animations:^{
487+
window.alpha = 0.0;
488+
}
489+
completion:^(BOOL finished) {
490+
window.hidden = YES;
491+
UIKit_ForceUpdateHomeIndicator();
492+
}];
493+
}
494+
495+
- (void)postFinishLaunch
496+
{
497+
[self performSelector:@selector(hideLaunchScreen) withObject:nil afterDelay:0.0];
498+
499+
SDL_SetiOSEventPump(true);
500+
exit_status = forward_main(forward_argc, forward_argv);
501+
SDL_SetiOSEventPump(false);
502+
503+
if (launchWindow) {
504+
launchWindow.hidden = YES;
505+
launchWindow = nil;
506+
}
507+
}
508+
509+
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0))
510+
{
511+
// This doesn't appear to be called, but it needs to be implemented to signal that we support the UIScene life cycle
512+
UISceneConfiguration *config = [[UISceneConfiguration alloc] initWithName:@"SDLSceneConfiguration" sessionRole:connectingSceneSession.role];
513+
config.delegateClass = [SDLUIKitSceneDelegate class];
514+
return config;
515+
}
516+
517+
@end // SDLUIKitSceneDelegate
518+
347519

348520
@implementation SDLUIKitDelegate
349521
{
@@ -514,6 +686,6 @@ - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDiction
514686
return YES;
515687
}
516688

517-
@end
689+
@end // SDLUIKitDelegate
518690

519691
#endif // SDL_VIDEO_DRIVER_UIKIT

src/video/uikit/SDL_uikitwindow.m

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,43 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, UIWindow
152152
return true;
153153
}
154154

155+
API_AVAILABLE(ios(13.0))
156+
static UIWindowScene *GetActiveWindowScene(void)
157+
{
158+
if (@available(iOS 13.0, tvOS 13.0, *)) {
159+
NSSet<UIScene *> *connectedScenes = [UIApplication sharedApplication].connectedScenes;
160+
161+
// First, try to find an active foreground scene
162+
for (UIScene *scene in connectedScenes) {
163+
if ([scene isKindOfClass:[UIWindowScene class]]) {
164+
UIWindowScene *windowScene = (UIWindowScene *)scene;
165+
if (windowScene.activationState == UISceneActivationStateForegroundActive) {
166+
return windowScene;
167+
}
168+
}
169+
}
170+
171+
// If no active scene, return any foreground scene
172+
for (UIScene *scene in connectedScenes) {
173+
if ([scene isKindOfClass:[UIWindowScene class]]) {
174+
UIWindowScene *windowScene = (UIWindowScene *)scene;
175+
if (windowScene.activationState == UISceneActivationStateForegroundInactive) {
176+
return windowScene;
177+
}
178+
}
179+
}
180+
181+
// Last resort: return first window scene
182+
for (UIScene *scene in connectedScenes) {
183+
if ([scene isKindOfClass:[UIWindowScene class]]) {
184+
return (UIWindowScene *)scene;
185+
}
186+
}
187+
}
188+
189+
return nil;
190+
}
191+
155192
bool UIKit_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props)
156193
{
157194
@autoreleasepool {
@@ -197,13 +234,21 @@ bool UIKit_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properti
197234
}
198235
#endif // !SDL_PLATFORM_TVOS
199236

200-
// ignore the size user requested, and make a fullscreen window
201-
// !!! FIXME: can we have a smaller view?
237+
UIWindow *uiwindow = nil;
238+
if (@available(iOS 13.0, tvOS 13.0, *)) {
239+
UIWindowScene *scene = GetActiveWindowScene();
240+
if (scene) {
241+
uiwindow = [[SDL_uikitwindow alloc] initWithWindowScene:scene];
242+
}
243+
}
244+
if (!uiwindow) {
245+
// ignore the size user requested, and make a fullscreen window
202246
#ifdef SDL_PLATFORM_VISIONOS
203-
UIWindow *uiwindow = [[SDL_uikitwindow alloc] initWithFrame:CGRectMake(window->x, window->y, window->w, window->h)];
247+
uiwindow = [[SDL_uikitwindow alloc] initWithFrame:CGRectMake(0, 0, SDL_XR_SCREENWIDTH, SDL_XR_SCREENHEIGHT)];
204248
#else
205-
UIWindow *uiwindow = [[SDL_uikitwindow alloc] initWithFrame:data.uiscreen.bounds];
249+
uiwindow = [[SDL_uikitwindow alloc] initWithFrame:data.uiscreen.bounds];
206250
#endif
251+
}
207252

208253
// put the window on an external display if appropriate.
209254
#ifndef SDL_PLATFORM_VISIONOS

0 commit comments

Comments
 (0)