Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ios/RNUnityView.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
#import <React/RCTView.h>
#import <React/RCTEventDispatcher.h>
#import <UIKit/UIKit.h>
#include <UnityFramework/UnityFramework.h>
#include <UnityFramework/NativeCallProxy.h>

// 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 <React/RCTViewComponentView.h>
Expand Down
52 changes: 48 additions & 4 deletions ios/RNUnityView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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<UISceneDelegate> sceneDelegate = scene.delegate;
if ([sceneDelegate respondsToSelector:@selector(window)]) {
UIWindow *window = [(id<UIWindowSceneDelegate>)sceneDelegate window];
if (window != nil && window != exclude) {
return window;
}
}

for (UIWindow *window in ((UIWindowScene *)scene).windows) {
if (window.isKeyWindow && window != exclude) {
return window;
}
}
}
}

id<UIApplicationDelegate> 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).
Expand Down Expand Up @@ -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];
}
}

Expand Down Expand Up @@ -201,6 +241,10 @@ - (void)prepareForRecycle {
[[self ufw] pause:true];
_pausedForRecycle = YES;
}

// <UnityView> 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 {
Expand Down
2 changes: 1 addition & 1 deletion ios/RNUnityViewManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down