Skip to content

fix(core): custom application delegate scene wiring and non-scene launch timing - #11367

Merged
NathanWalker merged 2 commits into
mainfrom
fix/custom-app-delegate-scenes
Aug 26, 2026
Merged

fix(core): custom application delegate scene wiring and non-scene launch timing#11367
NathanWalker merged 2 commits into
mainfrom
fix/custom-app-delegate-scenes

Conversation

@edusperoni

Copy link
Copy Markdown
Contributor

PR Checklist


Two follow-up fixes to #11181, both on iOS.

1. A custom application delegate broke scenes entirely

Current behavior

The two scene-related UIApplicationDelegate methods — applicationConfigurationForConnectingSceneSessionOptions and applicationDidDiscardSceneSessions — were installed only on the built-in Responder class at module load. The delegate setter just stored whatever it was given, and run() hands that class to UIApplicationMain.

So an app doing Application.ios.delegate = MyDelegate — common for push tokens, openURL, or an SDK that requires it — gave iOS a delegate with no scene methods at all, no scene configuration was ever returned, and scenes were dead. There is no workaround by subclassing: NativeScript cannot extend an already-extended native class, so class MyDelegate extends Responder is not available.

New behavior

The defaults are installed on whichever delegate class is in use, per method and only when absent, so a delegate that brings its own implementation still wins.

For the case where you want to handle some of it yourself and leave the rest to NativeScript, the defaults are now public and take the same arguments as the delegate methods:

@NativeClass()
class MyDelegate extends UIResponder implements UIApplicationDelegate {
  static ObjCProtocols = [UIApplicationDelegate];

  applicationConfigurationForConnectingSceneSessionOptions(app, session, options) {
    if (session.role === someCustomRole) {
      return myOwnConfiguration;
    }
    return Application.ios.defaultSceneConfiguration(app, session, options);
  }
}

defaultDiscardSceneSessions(application, sceneSessions) is available the same way.

Also handled:

  • window accessor. Responder provides one; a custom delegate loses it, and UIKit queries delegate.window on non-scene apps. One is installed only when the class has no prototype-level window at all. It stores what is assigned rather than copying Responder's NOOP setter — silently swallowing this.window = x on someone's own class would be worse than the original breakage.
  • static ObjCProtocols. Without it the ObjC runtime may never dispatch to the class. Protocols are a one-time class-build parameter, so this cannot be added after the fact — assigning a delegate class that does not declare UIApplicationDelegate now warns once, loudly, telling you to add the line.
  • Timing. The delegate is consumed by UIApplicationMain inside run(); assigning one afterwards warns, since it can no longer take effect.

Worth noting for anyone reading this who only wants to customise scene configuration: configureForScenes() already covers that without a custom delegate — the default consults it first and honours whatever it returns. This fix is for apps that need a custom delegate for unrelated reasons and lost the scene wiring as collateral.

2. launch no longer fired at didFinishLaunching on a background launch

Current behavior

#11181 changed two things about the non-scene startup path when only one was intended. shouldDelayLaunchEvent became a no-op, and the first window's content — and with it the legacy launch event — became automatically deferred whenever applicationState was Background at launch.

Previously the default fired launch and built the root view in applicationDidFinishLaunching regardless of background state, and deferral was strictly opt-in through the flag. Plugins rely on launch arriving at didFinishLaunching, background launches included — a silent push, for instance, needs handlers registered before it is processed.

New behavior

The automatic background deferral is gone. Content resolves in applicationDidFinishLaunching again, and delaying it is opt-in through shouldDelayLaunchEvent, which remains deprecated but works as it always did for non-scene apps.

mode shouldDelayLaunchEvent launch state result
non-scene false (default) foreground or background readywindowOpenlaunch + content, all in didFinishLaunching
non-scene true either readywindowOpen; launch + content on first didBecomeActive
scene either (unread) foreground ready in didFinishLaunching; launch + content with the first window's scene
scene either (unread) background, no scene connects ready only

ready is unchanged: still raised in didFinishLaunching on every path, before any window is registered.

Two pieces of deprecation text were factually wrong and are corrected — shouldDelayLaunchEvent's "has no effect", and launchEvent's claim that it does not fire for background launches, which now holds only for scene-based apps.

Tests

413 → 419 passing. The launch-timing specs drive the real didFinishLaunchingWithOptions and didBecomeActive on a fresh iOSApplication, and were mutation-verified: restoring the background check fails exactly the two tests that pin the restored behavior.

No new import cycles (unchanged at android 89 / ios 88).

Not covered by CI

Device-only: that protocol conformance actually reaches the installed IMPs on a custom delegate class, UIApplicationMain behavior with one, and a real background launch (silent push) firing launch at didFinishLaunching.

The scene configuration and discarded-session methods were installed only on
the built-in Responder class, so an app assigning its own delegate - which
cannot extend Responder, since NativeScript does not subclass an already
extended native class - left iOS with no scene configuration at all.

They are now installed on whichever delegate class is in use, per method, so a
delegate bringing its own implementation still wins; defaultSceneConfiguration()
and defaultDiscardSceneSessions() are public so such a delegate can handle the
cases it cares about and hand the rest back.
Non-scene apps deferred the first window's content - and with it the 'launch'
event - whenever the app launched into the background. Plugins rely on 'launch'
arriving while the app finishes launching, background launches included.

Content is resolved in applicationDidFinishLaunching again, and delaying it is
once more opt-in through shouldDelayLaunchEvent, which stays deprecated but
works as it always did for non-scene apps.
@nx-cloud

nx-cloud Bot commented Aug 26, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 047ab6a

Command Status Duration Result
nx test apps-automated -c=android ✅ Succeeded 3m 37s View ↗
nx run-many --target=test --configuration=ci --... ✅ Succeeded <1s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-08-26 20:17:58 UTC

@pkg-pr-new

pkg-pr-new Bot commented Aug 26, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@nativescript/core@11367
npm i https://pkg.pr.new/@nativescript/vite@11367
npm i https://pkg.pr.new/@nativescript/webpack@11367

commit: 047ab6a

@NathanWalker
NathanWalker merged commit 3277309 into main Aug 26, 2026
10 checks passed
@NathanWalker
NathanWalker deleted the fix/custom-app-delegate-scenes branch August 26, 2026 20:23
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.

2 participants