Skip to content

fix(iOS): drain UISceneConnectionOptions on cold launch (#21362)#21394

Merged
MrJul merged 1 commit into
AvaloniaUI:masterfrom
koblavi:fix/ios-scene-cold-launch-universal-link
May 19, 2026
Merged

fix(iOS): drain UISceneConnectionOptions on cold launch (#21362)#21394
MrJul merged 1 commit into
AvaloniaUI:masterfrom
koblavi:fix/ios-scene-cold-launch-universal-link

Conversation

@koblavi

@koblavi koblavi commented May 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #21362 — the cold-launch path of #17600 (#18005 only addressed the warm path).

When the iOS app uses scene-based lifecycle (iOS 13+), Apple delivers cold-launch user activities and URL contexts via UISceneConnectionOptions on scene:willConnectToSession:options:not via the warm-path selectors scene:continueUserActivity: or scene:openURLContexts:. AvaloniaSceneDelegate ignored connectionOptions entirely, so cold-launching the app from a Universal Link or custom-scheme URL dropped the URL on the floor.

The user's subscriber to IActivatableLifetime.Activated then only saw the base ActivatedEventArgs raised by the foreground transition (OnLeavingBackground), never a ProtocolActivatedEventArgs with the URL.

Change

AvaloniaSceneDelegate.WillConnect, after creating the window, drains both connectionOptions.UserActivities and connectionOptions.UrlContexts through the existing IAvaloniaAppInternalDelegate paths.

Also adds scene:openURLContexts: so warm custom-scheme URLs work under scene lifecycle — previously these were silently dropped too because application:openURL: on the AppDelegate is not called when scenes are in use (a smaller pre-existing bug surfaced by this same investigation).

The IAvaloniaAppInternalDelegate interface gains an OpenUrl(NSUrl) method so the scene delegate can route URL contexts through the same code path the existing application:openURL:options: AppDelegate handler uses. The AppDelegate's public OpenUrl selector method is refactored to a one-liner delegating to the explicit interface impl — no behaviour change for the AppDelegate-only path.

Test plan

The Avalonia.iOS.UnitTests / Avalonia.IntegrationTests.iOS paths in sparse-checkout return empty, so iOS scene-lifecycle test scaffolding doesn't currently exist in the repo to model from. Happy to add a test if maintainers can point at a fixture pattern.

Manual repro (from issue):

  1. Avalonia 12.0.2 iOS app with Associated Domains entitlement (applinks:<your-domain>) + matching AASA served.
  2. Subscribe to IActivatableLifetime.Activated in App.Initialize, log ProtocolActivatedEventArgs.Kind vs the bare type name.
  3. Build signed dev IPA, install to physical device. Force-quit the app (this is critical — warm activations already work).
  4. Tap a https://<your-domain>/... Universal Link from Messages / Mail.
  5. Before this fix: Activated fires with base ActivatedEventArgs, no URL surfaces. Device console: OnActivated fired: kind=ActivatedEventArgs.
  6. After this fix: Activated fires with ProtocolActivatedEventArgs.Kind == OpenUri carrying the URL.

For URL contexts (custom schemes via scene lifecycle): same flow, with a myapp://... URL instead of a Universal Link — before this PR these fall through entirely under scene lifecycle; after, they route to ProtocolActivatedEventArgs like the AppDelegate path.

References

🤖 PR drafted with assistance from Claude Code

…1362)

When the iOS app uses scene-based lifecycle (iOS 13+), Apple delivers
cold-launch user activities and URL contexts via UISceneConnectionOptions
on scene:willConnectToSession:options: — NOT via the warm-path selectors
scene:continueUserActivity: or scene:openURLContexts:. AvaloniaSceneDelegate
ignored connectionOptions entirely, so cold-launching the app from a
Universal Link or custom-scheme URL dropped the URL on the floor.

The user's subscriber to IActivatableLifetime.Activated then only saw
the base ActivatedEventArgs raised by the foreground transition
(OnLeavingBackground), never a ProtocolActivatedEventArgs with the URL.

Fix: in WillConnect, after the window is created and made key, drain
both connectionOptions.UserActivities and connectionOptions.UrlContexts
through the existing IAvaloniaAppInternalDelegate paths. Also add a
scene:openURLContexts: handler so warm custom-scheme URLs work under
scene lifecycle (previously these were silently dropped too —
application:openURL: on the AppDelegate is not called when scenes are
in use).

Closes AvaloniaUI#21362 (cold path of AvaloniaUI#17600 / AvaloniaUI#18005).

The IAvaloniaAppInternalDelegate interface gains an OpenUrl(NSUrl)
method so the scene delegate can route URL contexts through the same
code path the existing application:openURL:options: AppDelegate handler
uses.

No new tests — the Avalonia.iOS UnitTests and IntegrationTests projects
don't currently exist in the repo. Manual verification steps in the
issue: build any Avalonia.iOS app with Associated Domains entitlement
+ AASA served, force-quit, tap a Universal Link, observe the
Activated event fires with ProtocolActivatedEventArgs (not base
ActivatedEventArgs). Happy to add a test if maintainers can point at
a scene-lifecycle test fixture to model from.
@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.1.999-cibuild0065551-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@cla-avalonia

cla-avalonia commented May 19, 2026

Copy link
Copy Markdown
Collaborator
  • All contributors have signed the CLA.

@koblavi

koblavi commented May 19, 2026

Copy link
Copy Markdown
Contributor Author

@cla-avalonia agree

@koblavi

koblavi commented May 19, 2026

Copy link
Copy Markdown
Contributor Author

For triage when a maintainer gets to this — suggested labels:

  • bug (cold-launch URL drop is a regression vs the documented behavior of IActivatableLifetime)
  • os-ios

Ping @timunie since you already engaged on the parent issue #21362.

@MrJul MrJul added bug os-ios backport-candidate-12.0.x Consider this PR for backporting to 12.0 branch labels May 19, 2026

@MrJul MrJul left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@MrJul MrJul enabled auto-merge May 19, 2026 08:40
@MrJul MrJul added this pull request to the merge queue May 19, 2026
Merged via the queue into AvaloniaUI:master with commit 1db5837 May 19, 2026
12 checks passed
MrJul pushed a commit to MrJul/Avalonia that referenced this pull request May 28, 2026
…1362) (AvaloniaUI#21394)

When the iOS app uses scene-based lifecycle (iOS 13+), Apple delivers
cold-launch user activities and URL contexts via UISceneConnectionOptions
on scene:willConnectToSession:options: — NOT via the warm-path selectors
scene:continueUserActivity: or scene:openURLContexts:. AvaloniaSceneDelegate
ignored connectionOptions entirely, so cold-launching the app from a
Universal Link or custom-scheme URL dropped the URL on the floor.

The user's subscriber to IActivatableLifetime.Activated then only saw
the base ActivatedEventArgs raised by the foreground transition
(OnLeavingBackground), never a ProtocolActivatedEventArgs with the URL.

Fix: in WillConnect, after the window is created and made key, drain
both connectionOptions.UserActivities and connectionOptions.UrlContexts
through the existing IAvaloniaAppInternalDelegate paths. Also add a
scene:openURLContexts: handler so warm custom-scheme URLs work under
scene lifecycle (previously these were silently dropped too —
application:openURL: on the AppDelegate is not called when scenes are
in use).

Closes AvaloniaUI#21362 (cold path of AvaloniaUI#17600 / AvaloniaUI#18005).

The IAvaloniaAppInternalDelegate interface gains an OpenUrl(NSUrl)
method so the scene delegate can route URL contexts through the same
code path the existing application:openURL:options: AppDelegate handler
uses.

No new tests — the Avalonia.iOS UnitTests and IntegrationTests projects
don't currently exist in the repo. Manual verification steps in the
issue: build any Avalonia.iOS app with Associated Domains entitlement
+ AASA served, force-quit, tap a Universal Link, observe the
Activated event fires with ProtocolActivatedEventArgs (not base
ActivatedEventArgs). Happy to add a test if maintainers can point at
a scene-lifecycle test fixture to model from.
@MrJul MrJul added backported-12.0.x and removed backport-candidate-12.0.x Consider this PR for backporting to 12.0 branch labels May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[iOS] Cold-launch Universal Link raises base ActivatedEventArgs (no URL) — #17600 cold path still broken in 12.0.2

4 participants