Addresses four issues found while reviewing the squashed CarPlay + iOS
scene-support work on this branch. Each stems from the new multi-scene
lifecycle being split across the phone SceneDelegate, the CarPlay app/
dashboard scene delegates and CarPlayService.updateMapHost, with a few
decisions not centralized so they drifted.
--- #1 Per-session car defaults now reset when leaving all car screens ---
hasAppliedDefaultCarZoom / hasEngagedInitialCarFollow were reset only in
CarPlayService.destroy(), which runs on CarPlay app-scene disconnect. A
dashboard that connected, applied the defaults, disconnected and reconnected
within the same app launch skipped the driving-friendly overview zoom and the
initial heading-up follow on its second session. Reset is now routed through a
single resetCarSessionDefaults() helper, called both from destroy() and from
updateMapHost() whenever the map settles back on the phone (i.e. leaves all car
screens).
Verify:
- Connect CarPlay, then in the dashboard tile confirm the map zooms out to
the overview level and starts heading-up follow. - Disconnect and reconnect the dashboard without quitting the app.
- Before: second dashboard session keeps the previous (close) zoom and does
not re-engage follow. - After: second session re-applies the overview zoom and heading-up follow.
--- #2 Car-first launch runs the app-active service refresh ---
The phone path ran MWMSearch.addCategoriesToSpotlight,
MWMKeyboard.applicationDidBecomeActive and MWMTextToSpeech.applicationDidBecomeActive
on activation; the CarPlay path (SceneLifecycle.carSceneDidBecomeActive) did
not, so on a launch made directly into CarPlay (no phone scene) those never
ran until a phone scene later activated. The three calls are centralized in a
new -[MapsAppDelegate handleDidBecomeActive] (mirroring handleDidEnterBackground)
and invoked from both the phone sceneDidBecomeActive and the CarPlay path, so
they can't drift again.
Verify:
- With the phone locked/not launched, start the app directly into CarPlay and
begin a route. - Before: TTS voice state / Spotlight categories are not refreshed on the
car-first activation (only after a phone scene becomes active). - After: handleDidBecomeActive runs on the CarPlay activation; voice guidance
and Spotlight indexing are refreshed without needing the phone scene.
--- #3 Phone resign uses a shared, resign-aware keep-foreground check ---
SceneDelegate.sceneWillResignActive guarded only on isHostingMapOnCarScreen and
then called enterBackgroundIfNeeded directly, bypassing the shouldKeepForeground
predicate the CarPlay path funnels through. It could not reuse
leaveForegroundIfNoSceneActive because at willResignActive the resigning scene
is still .foregroundActive, so isPhoneSceneForegroundActive would wrongly report
the engine as needed. Added SceneLifecycle.shouldKeepForeground(whenResigning:)
which excludes the resigning scene, and routed the phone path through it.
Behavior on iPhone is unchanged (single phone scene, so locking with a
non-hosting CarPlay placeholder still backgrounds the engine — correct for
battery); the asymmetric hand-rolled decision is gone and it is now
multi-window safe.
Verify:
- Connect CarPlay with the map hosted on the car screen, lock the phone:
rendering and location updates keep running (unchanged). - Choose "Continue on phone" so CarPlay shows the placeholder, lock the phone:
the engine backgrounds and restores on unlock (unchanged). - Watch the [CarPlayHost] / sceneWillResignActive logs to confirm the decision
now comes from shouldKeepForegroundWhenResigning: rather than the ad-hoc
isHostingMapOnCarScreen check.
--- #4 MapsAppDelegate.window no longer rescans connectedScenes ---
-[MapsAppDelegate window] looped UIApplication.connectedScenes on every access,
and window is read on hot layout paths (TabBarArea.areaFrame, the theme
renderers). There is only ever one phone SceneDelegate, so the scan was waste.
The connected phone scene delegate is now cached in a weak activeSceneDelegate
property that SceneDelegate sets on connect and clears on disconnect; the getter
reads it directly. Weak so it auto-nils if a disconnect is ever missed; the
value still resolves to nil during a car-first launch.
Verify:
- Normal phone launch: map UI lays out and light/dark theme switching still
works (these read theApp.window). - Car-first launch: theApp.window is nil and the map attaches to the CarPlay
window as before (see the phoneWindow=... field in the updateMapHost log). - Before: every window access walks connectedScenes.
- After: window access is a single weak-pointer read.
🤖 Generated with Claude Code
Signed-off-by: eisa01 eisa01@gmail.com
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com