Repro: launch CoMaps from CarPlay, switch the CarPlay screen to Apple Maps
and start navigation, switch to Google Maps and start navigation, wait a
couple of minutes, then switch back to CoMaps. CoMaps' CarPlay map has lost
its position fix and the blue dot never recovers — until the phone is
unlocked and the CoMaps phone app is foregrounded, at which point the fix
returns immediately.
Root cause (confirmed in code and in device/simulator logs that show both
CarPlay scene callbacks firing with activationState == 0 / .unattached):
the framework and location lifecycle were keyed off a CarPlay scene's
activationState, which UIKit reports unreliably. Three distinct gaps
combined to produce the symptom.
-
Location was killed while the map was hosted on a CarPlay screen.
When the phone is locked (or otherwise not foreground), the phone scene's
sceneWillResignActive checked SceneLifecycle.isAnyCarSceneForegroundActive,
which requires a CarPlay scene to report .foregroundActive. Because that
state is unreliable (the app scene stays .foregroundInactive in the
simulator and dashboard<->app handoffs race), the guard failed, the full
resign path ran, keepRunningInBackground() returned NO (CoMaps itself was
not routing), and location was stopped — logged as "Stop updating
location" even though the map was on the car screen. -
Background location updates were never enabled for the CarPlay case.
allowsBackgroundLocationUpdates was set only inside applicationWillResignActive,
and keepRunningInBackground() only returned YES when CoMaps itself was
routing or GPS-tracking. With the user navigating in Apple/Google Maps —
not CoMaps — CoMaps was a fully backgrounded app with background updates
disabled, so iOS suspended it and stopped delivering fixes; the cached
fix went stale over the couple of minutes the other app held the screen. -
Location was never restarted on the CarPlay foreground path.
carSceneDidBecomeActive() restored rendering and focus but never called
applicationDidBecomeActive. Only the phone scene's sceneDidBecomeActive
(re)started location — which is exactly why the fix recovered only after
foregrounding the phone app, and not when switching back to CoMaps on
CarPlay.
Fix, in three parts:
Part A — key the lifecycle on stable host state instead of activationState.
- CarPlayService.isHostingMapOnCarScreen (mapHost == .carplay/.dashboard),
which only flips when the map view is actually re-parented. - SceneLifecycle.shouldKeepForeground = phone scene foregroundActive
(reliable for a normal app scene) OR isHostingMapOnCarScreen. - SceneDelegate.sceneWillResignActive keeps the framework and location
alive based on isHostingMapOnCarScreen. - leaveForegroundIfNoSceneActive() runs the full background transition
(onGetFocus(false) + applicationWillResignActive + EnterBackground) and
is invoked from both carSceneDidEnterBackground and the end of
CarPlayService.destroy(), so it is robust to UIKit skipping
sceneDidEnterBackground for a disconnecting CarPlay scene (which would
otherwise leave rendering and GPS running on a locked phone).
This half mirrors the approach from the earlier, un-merged commit
578b9ef2d2 on the carplay-background-host-state branch.
Part B — keep background location alive while CarPlay hosts the map.
- keepRunningInBackground() now also returns YES while
isHostingMapOnCarScreen, so CoMaps stays eligible for background
location and is not suspended when another CarPlay nav app takes the
screen with the phone locked. - The allowsBackgroundLocationUpdates policy is centralized in
applyBackgroundLocationUpdatesPolicy and applied on BOTH lifecycle edges
(applicationDidBecomeActive and applicationWillResignActive), not just on
resign, so the CarPlay-hosted case enables background updates when
location starts. The flag reverts to NO on CarPlay disconnect, when
keepRunningInBackground() again returns NO.
Part C — restart location on the CarPlay foreground path.
- carSceneDidBecomeActive() now also calls
LocationManager.applicationDidBecomeActive(), mirroring the phone scene.
This recovers the fix if the app was suspended while another nav app held
the screen, and (with Part B) re-asserts the background-updates policy.
Shared by both the CarPlay app and dashboard scene delegates.
Safety for phone-only users: when CarPlay is disconnected,
isHostingMapOnCarScreen is false and keepRunningInBackground() is unchanged,
so normal phone backgrounding still stops location when not routing — no
battery regression.
Also logs the framework EnterForeground/EnterBackground transitions and the
keep/leave-foreground decisions, which were previously invisible.
Verification requires a real device (the simulator cannot switch between
CarPlay apps and reports kCLErrorDenied artifacts): reproduce the sequence
above and confirm the blue dot is live the instant CoMaps returns to the
CarPlay screen without touching the phone; confirm location still stops on
CarPlay disconnect on a locked phone, and still stops on a plain phone lock
when not routing.
Generated with Claude Code Opus 4.8
Signed-off-by: eisa01 eisa01@gmail.com
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com