diff --git a/ios/RNUnityView.h b/ios/RNUnityView.h index 18afa00..176c6ba 100644 --- a/ios/RNUnityView.h +++ b/ios/RNUnityView.h @@ -1,8 +1,13 @@ #import #import +#import #include #include +// The host app's window. Under the UIScene life cycle (required by the iOS 27 SDK, +// default in Expo SDK 58) the app delegate owns no window, so `delegate.window` is nil. +FOUNDATION_EXTERN UIWindow * _Nullable RNUnityHostWindow(UIView * _Nullable view, UIWindow * _Nullable exclude); + // This guard prevent this file to be compiled in the old architecture. #ifdef RCT_NEW_ARCH_ENABLED #import diff --git a/ios/RNUnityView.mm b/ios/RNUnityView.mm index 6406f64..d66c417 100644 --- a/ios/RNUnityView.mm +++ b/ios/RNUnityView.mm @@ -32,6 +32,44 @@ return ufw; } +UIWindow* RNUnityHostWindow(UIView* view, UIWindow* exclude) { + if (view.window != nil && view.window != exclude) { + return view.window; + } + + if (@available(iOS 13.0, *)) { + for (UIScene *scene in [[UIApplication sharedApplication] connectedScenes]) { + // Foreground-inactive still counts: unload can arrive while a system overlay is up. + if (scene.activationState == UISceneActivationStateUnattached || + scene.activationState == UISceneActivationStateBackground || + ![scene isKindOfClass:[UIWindowScene class]]) { + continue; + } + + id sceneDelegate = scene.delegate; + if ([sceneDelegate respondsToSelector:@selector(window)]) { + UIWindow *window = [(id)sceneDelegate window]; + if (window != nil && window != exclude) { + return window; + } + } + + for (UIWindow *window in ((UIWindowScene *)scene).windows) { + if (window.isKeyWindow && window != exclude) { + return window; + } + } + } + } + + id appDelegate = [[UIApplication sharedApplication] delegate]; + if ([appDelegate respondsToSelector:@selector(window)] && appDelegate.window != exclude) { + return appDelegate.window; + } + + return nil; +} + @implementation RNUnityView { // YES once the Fabric view paused Unity for recycling, so we know to resume // the shared instance when the view comes back on screen (#180). @@ -134,13 +172,15 @@ - (void)resumeUnity { - (void)unloadUnity { _unloaded = YES; _pausedForRecycle = NO; - UIWindow * main = [[[UIApplication sharedApplication] delegate] window]; + UIWindow * unityWindow = [self unityIsInitialized] ? [[[self ufw] appController] window] : nil; + UIWindow * main = RNUnityHostWindow(self, unityWindow); if(main != nil) { [main makeKeyAndVisible]; + } - if([self unityIsInitialized]) { - [[self ufw] unloadApplication]; - } + // Unload even without a host window: skipping it silently leaves the engine resident. + if([self unityIsInitialized]) { + [[self ufw] unloadApplication]; } } @@ -201,6 +241,10 @@ - (void)prepareForRecycle { [[self ufw] pause:true]; _pausedForRecycle = YES; } + + // calls unloadUnity on unmount. The recycled instance serves the next + // mount, so clear the teardown latch or layoutSubviews never starts Unity again. + _unloaded = NO; } + (ComponentDescriptorProvider)componentDescriptorProvider { diff --git a/ios/RNUnityViewManager.mm b/ios/RNUnityViewManager.mm index 4c56a29..defc7cc 100644 --- a/ios/RNUnityViewManager.mm +++ b/ios/RNUnityViewManager.mm @@ -17,7 +17,7 @@ @implementation RNUnityViewManager - (UIView *)view { unity = [[RNUnityView alloc] init]; - UIWindow * main = [[[UIApplication sharedApplication] delegate] window]; + UIWindow * main = RNUnityHostWindow(nil, nil); if(main != nil) { [main makeKeyAndVisible];