Skip to content

Add userTrackingMode: follow and course tracking that behave the same on every provider #185

Description

@jkasprzyk17

Part of #183 (live navigation, phase 2). Builds on #184.

Problem

followsUserLocation behaves differently on every provider:

No provider can turn the camera with the direction of travel, which is what a navigation camera needs most.

Proposed API

type UserTrackingMode = 'none' | 'follow' | 'course' | 'compass';

/** How the camera follows the user. Needs `showsUserLocation`. */
userTrackingMode?: UserTrackingMode;

/** Zoom and pitch to hold while tracking. A value that is left out keeps the current camera's value. */
userTrackingCamera?: { zoom?: number; pitch?: number };

/** Called when the map changes the tracking mode on its own: today, only to 'none' after a user gesture. */
onUserTrackingModeChange?: (mode: UserTrackingMode) => void;
  • follow keeps the user at the camera target and leaves the heading alone.
  • course does the same and rotates the map so that the direction of travel points up. While the course is unknown, for example when standing still, it keeps the last one instead of snapping to north.
  • compass rotates the map with the device heading. MapKit has this built in (.followWithHeading). The Google Maps SDKs need the device's heading sensor, so this mode can come after the other two.
  • The camera target honours mapPadding; that is how an app puts the user low on screen to show the road ahead. Google Maps already moves the target with the padding. For MapKit, check whether .follow honours layoutMargins, and if it does not, follow manually with MKMapCamera. course is manual on MapKit either way, because MapKit has no course mode.
  • followsUserLocation={true} becomes a deprecated alias for userTrackingMode="follow", and the Android warning added for Android silently ignores followsUserLocation and showsScale #104 goes away.

Gestures

On every provider, a user gesture (pan, zoom, rotate or tilt) ends tracking. The native side switches to none and calls onUserTrackingModeChange('none'). The app stores that in its state and sets the mode again, for example from a "re-center" button.

This contract has to be documented. If JS keeps passing 'course' after the native side dropped to none, the prop is not sent again, because from React's point of view it did not change.

Alternative considered: imperative ref.startUserTracking(mode, options) and ref.stopUserTracking() methods plus the same event. That is where @rnmapbox/maps ended up (Viewport.transitionTo and idle, onStatusChanged). A prop fits this library's camera and region props better; revisit if keeping the state in sync turns out to be error-prone.

Update rate and smoothness

While tracking, Android should ask for updates about once a second (see #184). Each fix moves the camera from native code, with no JS round trip. Check smoothness at one update per second on a simulated drive. On Android, animateCamera takes a duration but no easing curve, and a new animation cancels the one in progress.

Native mapping

Provider follow course compass Detecting a user gesture
Apple .follow, or manual MKMapCamera manual MKMapCamera with the location's course as heading .followWithHeading mapView(_:didChange:animated:) for MapKit's own modes; isUserInteracting in regionWillChangeAnimated for the manual ones
Google iOS camera animation on the myLocation KVO that already exists the same, with bearing set to the course CLLocationManager heading updates mapView(_:willMove:) with gesture == true
Google Android animateCamera from FusedLocationSource the same, with bearing set to the course rotation-vector sensor OnCameraMoveStartedListener.REASON_GESTURE, already wired

Related

Acceptance criteria

  • follow and course behave the same on Apple, Google iOS and Google Android
  • A user gesture ends tracking on every provider and fires onUserTrackingModeChange('none') once
  • Setting the mode again after that resumes tracking
  • course keeps the last known course while standing still
  • The camera target honours mapPadding on every provider
  • Camera moves caused by tracking follow the same region-event rules as other programmatic camera moves
  • Tracking stops without errors when showsUserLocation turns off or permission is revoked
  • followsUserLocation still works as an alias, and its Android warning from Android silently ignores followsUserLocation and showsScale #104 is gone

Testing

  • Unit tests for the mode state machine and for holding the last course, as pure Kotlin and Swift.
  • At runtime: "Freeway Drive" in the iOS Simulator on both iOS providers and GPX playback on the Android emulator, with a manual pan during tracking on each provider.

Not in scope

A navigation-style user marker, zoom that changes with speed, look-ahead beyond mapPadding, and snapping to the route. These are listed under "Later" in #183.

Activity

  1. added
    enhancementNew feature or request
    swiftThe Swift / iOS native layer (package/ios)
    kotlinThe Kotlin / Android native layer (package/android)
    typescriptThe TypeScript layer (package/src)
    provider: appleAffects the Apple Maps (MapKit) provider
    provider: googleAffects the Google Maps provider
    on Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestkotlinThe Kotlin / Android native layer (package/android)platform: androidAffects Androidplatform: iosAffects iOSprovider: appleAffects the Apple Maps (MapKit) providerprovider: googleAffects the Google Maps providerswiftThe Swift / iOS native layer (package/ios)typescriptThe TypeScript layer (package/src)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions