Skip to content

fix(ios): find the host window under the UIScene life cycle - #193

Draft
AsterFerre wants to merge 2 commits into
azesmway:mainfrom
Quasar-Studio:upstream-pr/ios-scene-unload
Draft

AsterFerre wants to merge 2 commits into
azesmway:mainfrom
Quasar-Studio:upstream-pr/ios-scene-unload

Conversation

@AsterFerre

@AsterFerre AsterFerre commented Oct 9, 2026 •

Copy link
Copy Markdown

Problem

unloadUnity() resolves the host window through UIApplication.sharedApplication.delegate.window, and so does RNUnityViewManager's view.

Under the UIScene life cycle that property is nil. The window belongs to the SceneDelegate. Apple requires the scene-based life cycle for apps built with the iOS 27 SDK (Apple guide), and Expo SDK 58 enables it by default (expo.fyi/ios-scene-lifecycle).

Because the whole body of unloadUnity() sits inside if (main != nil), unloadApplication is never called in that setup. There is no error: onPlayerUnload never fires and Unity stays resident.

Fix

  • A new helper, RNUnityHostWindow(view, exclude), looks for the host window in this order:

    1. the view's own window;
    2. a foreground (active or inactive) UIWindowScene, via its delegate's window and then its key window;
    3. delegate.window, as before.

    Unity's own appController.window is excluded.

  • unloadUnity() still makes the host window key when it finds one. It now calls unloadApplication regardless.

  • RNUnityViewManager (old architecture) uses the same helper.

  • Remount after unload. <UnityView> calls unloadUnity on unmount, so once the unload really runs, Fabric hands the recycled view instance to the next mount with _unloaded still YES, and Unity never restarts. prepareForRecycle now clears it. (Second commit; non-scene apps hit the same path today.)

Apps without scenes behave as before: the first two lookups return the same window delegate.window used to.

Known limit: with several scenes on iPad and a view already detached from its window, the fallback can pick another scene's window. view.window covers the normal command path.

Testing

⚠️ I haven't compiled or run this yet: no macOS machine was available. The build-ios CI job currently fails at pod install (boost) before compiling, so CI can't confirm it either. That is why this is a draft. I'll mark it ready once it has been verified on a device:

  1. Build an Expo SDK 58 dev client (scene life cycle on) with a UnityFramework export.
  2. Mount <UnityView>, call unloadUnity(), and confirm onPlayerUnload fires.
  3. Navigate away and back (unmount → remount) and confirm Unity renders again.
  4. Repeat on a project without scenes, as a regression check.

Feedback on the approach is welcome before then.

unloadUnity() and the old-arch view manager looked up the host window
via UIApplication.delegate.window. Under the UIScene life cycle (required
by the iOS 27 SDK, default in Expo SDK 58) the window belongs to the
SceneDelegate and delegate.window is nil, so unloadUnity() skipped
unloadApplication entirely and Unity stayed resident.

RNUnityHostWindow() resolves the host window from the view itself, then
the foreground scene's delegate window or key window, then the app
delegate as before. unloadUnity() now unloads even when no host window
is found.
<UnityView> calls unloadUnity on unmount. With the host-window fix the unload
now really happens under the UIScene life cycle, but Fabric recycles the view
and _unloaded stayed YES, so the next mount never restarted Unity. Clear it in
prepareForRecycle.

The host-window scene lookup also accepts foreground-inactive scenes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant