Adopt the iOS UIScene lifecycle - #6179
Conversation
9edd5d1 to
4119db1
Compare
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
4119db1 to
43ac2b5
Compare
📸🪓 Test evidence (verification gaps closed)
badge setbadgecount 🪓 HACK-FORCED: universal link cold launch 🪓 HACK-FORCED: quick action cold launch 🪓 HACK-FORCED: quick action warm before 🪓 HACK-FORCED: quick action warm after 🪓 HACK-FORCED: universal link warm continue Captured by the agent's in-app test run (build-and-test). |
43ac2b5 to
c05e922
Compare
c05e922 to
f661dbc
Compare
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
iOS 26 deprecates the UIApplicationDelegate window and lifecycle callbacks once an app ships a scene manifest, and Xcode 27 is expected to remove the UIDesignRequiresCompatibility opt-out we currently rely on. Move window creation, foreground/background transitions, deep links and home-screen quick actions onto a new SceneDelegate, and leave AppDelegate owning only app-wide concerns. React Native 0.79 has no scene-aware entry points, so the scene rebuilds the launch options RCTLinkingManager.getInitialURL reads from the scene connection options. Without that, a cold-launch deep link would resolve to null. Also replaces applicationIconBadgeNumber, deprecated since iOS 17.
Records what the UIScene adoption covers, the two React Native 0.79 workarounds it needed, how it was verified on the simulator, and the surfaces that still have to be normalized before Xcode 27 removes the UIDesignRequiresCompatibility opt-out.
f661dbc to
bd90701
Compare











CHANGELOG
Does this branch warrant an entry to the CHANGELOG?
Dependencies
none
Requirements
If you have made any visual changes to the GUI. Make sure you have:
Description
Asana: https://app.asana.com/0/1215088146871429/1214464087258597
Adopts the iOS
UIScenelifecycle, which is the first of the Track 2 items onthat task and the one that does not depend on the React Native 0.86 upgrade.
iOS 26 deprecates the
UIApplicationDelegatewindow and lifecycle callbacks oncean app ships a scene manifest, and Apple removes the
UIDesignRequiresCompatibilityopt-out in Xcode 27 (around September 2026), which is the deadline this is sized
against. The task audit flagged three deprecated call sites in
AppDelegate.swift;all three are the scene work:
UIWindow(frame: UIScreen.main.bounds),applicationDidEnterBackground, andapplicationWillEnterForeground.What changed:
ios/edge/SceneDelegate.swiftowns the window, the foreground/backgroundprivacy overlay, deep links, and home-screen quick actions.
killing the process) reattaches the running root view controller into its new
window instead of starting React Native a second time, and forwards whatever
arrived with the reconnect (quick action, URL contexts, user activities)
through the running-app handlers, since only the first mount reads launch
options.
Info.plistdeclares a single-sceneUIApplicationSceneManifest;AppDelegatevends the matching configuration and keeps app-wide duties(Sentry, Firebase, background fetch, the React Native factory).
applicationIconBadgeNumber, deprecated since iOS 17, becomesUNUserNotificationCenter.setBadgeCount, guarded for the iOS 15.6 deploymenttarget.
src/docs/ios-26-scene-lifecycle-migration.mdrecords what is still owed beforeXcode 27, with a per-surface list for the compatibility-flag removal.
Two React Native 0.79 details are worth a reviewer's attention:
RCTLinkingManagerhas no scene entry points, andgetInitialURLreads thecold-launch URL from
bridge.launchOptions. iOS stops putting deep-link keysinto
didFinishLaunchingWithOptionsonce a scene manifest exists, so the scenerebuilds those launch options from
UIScene.ConnectionOptions. Without it,Linking.getInitialURL()returns null on every cold-launch deep link andnothing crashes to signal it.
AppDelegatekeeps awindowproperty.@react-native-firebase/messagingreads
application.delegate.windowonUIApplicationDidFinishLaunchingNotification; a delegate with nowindowatall raises
doesNotRecognizeSelectorand the app aborts on launch. The scenemirrors its window into it.
react-native-contacts,react-native-shareandRN's own LogBox read the same property, so the mirror covers them too.
Out of scope here, and tracked in the doc: removing
UIDesignRequiresCompatibility(needs full-app visual QA on iOS 26) and the React Navigation upgrade (sequence it
after React Native 0.86).
Testing
Driven on an iOS 18.6 simulator against a debug build. Temporary logging and
input-forcing harnesses were used where noted, and all of it was removed before
commit;
git statusis clean of them.Driven with no forcing:
creation and the React Native start path work under the scene.
sceneDidEnterBackground; the next foreground observes theLaunchScreen privacy overlay present and removes it.
edge://...arrives withUIApplicationLaunchOptionsURLKeypopulated and the app raises its own "Unknown deep link format" banner, proving
the URL reached JS through
getInitialURL.edge://...reachesscene(_:openURLContexts:).setBadgeCountrenders the requested badge on the home-screen icon.xcodebuild -configuration Release).Driven with a forced input, because the OS cannot deliver the real trigger here:
Home-screen quick actions, warm and cold. SpringBoard's icon context menu does
not open under the test harness, so the real registered item (
contact_support,read back out of
UIApplication.shortcutItems) was replayed throughwindowScene(_:performActionFor:)and through the coldconnectionOptions.shortcutItempath. Both end in Safari on the action's URL.Universal links, warm and cold.
edge.appserves a 404 at/.well-known/apple-app-site-associationand Apple's CDN has no entry for thedomain, so iOS never hands the app a browsing-web activity. A synthesized
NSUserActivitywas pushed through the reallaunchOptions(from:)and throughscene(_:continue:); the app answered "Parsing link..." and "No wallets existthat support this link." respectively.
Scene disconnect and reconnect. iOS will not disconnect a single-scene iPhone
app on demand, so the old window was released and the real
scene(_:willConnectTo:)was replayed on a freshSceneDelegateinstance, theway the OS does it. The same
UIViewControllercame back in the new window andthe app stayed live on the Buy scene with its quote still refreshing: no second
mount, no BootSplash re-run, no re-login.
That association gap is pre-existing and unrelated to this change, but it does mean
universal links are dead app-wide until an AASA is published.
Note
Medium Risk
Touches the iOS launch path, deep linking, and background privacy overlay with hand-rolled RN/Firebase compatibility shims that must stay correct across cold launch, warm links, and scene reconnect.
Overview
Moves the iOS app onto the UIScene lifecycle so window creation and scene callbacks are no longer on the deprecated
AppDelegatepath required before Xcode 27.A new
SceneDelegateowns the window, the LaunchScreen privacy overlay on background/foreground, deep links and universal links (viaRCTLinkingManager), and home-screen quick actions.AppDelegatekeeps global setup (Sentry, Firebase, RN factory, background fetch) and returns the scene configuration fromInfo.plist’s single-scene manifest. On scene reconnect, the existing React root is reattached instead of starting RN again, with late-arriving URLs/quick actions routed through the warm handlers.Two RN 0.79 shims ship with this: rebuild
launchOptionsfromUIScene.ConnectionOptionsso cold-startLinking.getInitialURL()still works, and mirror the scene window ontoAppDelegate.windowso Firebase Messaging and other libraries that readdelegate.windowdo not crash at launch. Background-fetch badge updates useUNUserNotificationCenter.setBadgeCounton iOS 16+ instead of the deprecated icon badge API.src/docs/ios-26-scene-lifecycle-migration.mddocuments follow-up work (e.g. removingUIDesignRequiresCompatibility) before the Xcode 27 deadline.Reviewed by Cursor Bugbot for commit bd90701. Bugbot is set up for automated code reviews on this repo. Configure here.