Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public struct CarPlayNavigationView: View {
let styleURL: URL
@Binding var camera: MapViewCamera
let navigationCamera: MapViewCamera
let locationManagerConfiguration: NavigationLocationManagerConfiguration?

private let navigationState: NavigationState?
private let userLayers: [StyleLayerDefinition]
Expand All @@ -42,11 +43,13 @@ public struct CarPlayNavigationView: View {
camera: Binding<MapViewCamera>,
navigationCamera: MapViewCamera = .automotiveNavigation(),
navigationState: NavigationState?,
locationManagerConfiguration: NavigationLocationManagerConfiguration? = nil,
minimumSafeAreaInsets: EdgeInsets = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16),
@MapViewContentBuilder makeMapContent: () -> [StyleLayerDefinition] = { [] }
) {
self.styleURL = styleURL
self.navigationState = navigationState
self.locationManagerConfiguration = locationManagerConfiguration
self.minimumSafeAreaInsets = minimumSafeAreaInsets

userLayers = makeMapContent()
Expand All @@ -61,6 +64,7 @@ public struct CarPlayNavigationView: View {
styleURL: styleURL,
camera: $camera,
navigationState: navigationState,
locationManagerConfiguration: locationManagerConfiguration,
onStyleLoaded: { _ in
camera = navigationCamera
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import CoreLocation
import MapLibre
import MapLibreSwiftUI

/// A map location manager that can be directly fed by navigation state updates.
public protocol NavigationDrivenLocationManager: MLNLocationManager, AnyObject {
var lastLocation: CLLocation { get set }
var lastHeading: CLHeading? { get set }
}

extension StaticLocationManager: NavigationDrivenLocationManager {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import MapLibre
import MapLibreSwiftUI

/// Configures location managers used by Ferrostar map navigation views.
///
/// - `nonNavigatingLocationManager`:
/// - `nil` uses MapLibre's default internal manager (recommended default).
/// - any custom manager can be supplied for non-navigation behavior.
/// - `navigatingLocationManager`:
/// - custom manager fed by Ferrostar navigation state.
///
/// Note: `MLNLocationManager` implementations are reference types. Do not construct this configuration
/// inline in a SwiftUI `body`; keep manager instances in stable state/model storage and pass references here.
public struct NavigationLocationManagerConfiguration {
public var nonNavigatingLocationManager: (any MLNLocationManager)?
public var navigatingLocationManager: any NavigationDrivenLocationManager

public init(
nonNavigatingLocationManager: (any MLNLocationManager)? = nil,
navigatingLocationManager: any NavigationDrivenLocationManager
) {
self.nonNavigatingLocationManager = nonNavigatingLocationManager
self.navigatingLocationManager = navigatingLocationManager
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public struct DynamicallyOrientingNavigationView: View {
let styleURL: URL
@Binding var camera: MapViewCamera
let navigationCamera: MapViewCamera
let locationManagerConfiguration: NavigationLocationManagerConfiguration?

private let navigationState: NavigationState?
private let userLayers: [StyleLayerDefinition]
Expand Down Expand Up @@ -48,6 +49,7 @@ public struct DynamicallyOrientingNavigationView: View {
camera: Binding<MapViewCamera>,
navigationCamera: MapViewCamera = .automotiveNavigation(),
navigationState: NavigationState?,
locationManagerConfiguration: NavigationLocationManagerConfiguration? = nil,
isMuted: Bool,
minimumSafeAreaInsets: EdgeInsets = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16),
onTapMute: @escaping () -> Void,
Expand All @@ -56,6 +58,7 @@ public struct DynamicallyOrientingNavigationView: View {
) {
self.styleURL = styleURL
self.navigationState = navigationState
self.locationManagerConfiguration = locationManagerConfiguration
self.isMuted = isMuted
self.minimumSafeAreaInsets = minimumSafeAreaInsets
self.onTapMute = onTapMute
Expand All @@ -74,6 +77,7 @@ public struct DynamicallyOrientingNavigationView: View {
styleURL: styleURL,
camera: $camera,
navigationState: navigationState,
locationManagerConfiguration: locationManagerConfiguration,
onUserTrackingModeChanged: { mode, _ in
userTrackingMode = mode
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public struct LandscapeNavigationView: View {
let styleURL: URL
@Binding var camera: MapViewCamera
let navigationCamera: MapViewCamera
let locationManagerConfiguration: NavigationLocationManagerConfiguration?

private let navigationState: NavigationState?
private let userLayers: [StyleLayerDefinition]
Expand Down Expand Up @@ -48,6 +49,7 @@ public struct LandscapeNavigationView: View {
camera: Binding<MapViewCamera>,
navigationCamera: MapViewCamera = .automotiveNavigation(),
navigationState: NavigationState?,
locationManagerConfiguration: NavigationLocationManagerConfiguration? = nil,
isMuted: Bool,
minimumSafeAreaInsets: EdgeInsets = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16),
onTapMute: @escaping () -> Void,
Expand All @@ -56,6 +58,7 @@ public struct LandscapeNavigationView: View {
) {
self.styleURL = styleURL
self.navigationState = navigationState
self.locationManagerConfiguration = locationManagerConfiguration
self.isMuted = isMuted
self.minimumSafeAreaInsets = minimumSafeAreaInsets
self.onTapMute = onTapMute
Expand All @@ -73,6 +76,7 @@ public struct LandscapeNavigationView: View {
styleURL: styleURL,
camera: $camera,
navigationState: navigationState,
locationManagerConfiguration: locationManagerConfiguration,
onUserTrackingModeChanged: { mode, _ in
userTrackingMode = mode
},
Expand Down
28 changes: 22 additions & 6 deletions apple/Sources/FerrostarMapLibreUI/Views/NavigationMapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ public struct NavigationMapView: View {
// TODO: Configurable camera and user "puck" rotation modes

private let navigationState: NavigationState?

@State private var locationManager = StaticLocationManager(initialLocation: CLLocation())
private let locationManagerConfiguration: NavigationLocationManagerConfiguration?
@State private var defaultNavigatingLocationManager: any NavigationDrivenLocationManager =
StaticLocationManager(initialLocation: CLLocation())

// MARK: Camera Settings

Expand All @@ -39,12 +40,15 @@ public struct NavigationMapView: View {
/// - styleURL: The map's style url.
/// - camera: The camera binding that represents the current camera on the map.
/// - navigationState: The current ferrostar navigation state provided by ferrostar core.
/// - locationManagerConfiguration: Optional custom managers for navigating/non-navigating modes.
/// Keep manager instances stable (do not construct inline in `body`).
/// - onStyleLoaded: The map's style has loaded and the camera can be manipulated (e.g. to user tracking).
/// - makeMapContent: Custom maplibre symbols to display on the map view.
public init(
styleURL: URL,
camera: Binding<MapViewCamera>,
navigationState: NavigationState?,
locationManagerConfiguration: NavigationLocationManagerConfiguration? = nil,
activity: MapActivity = .standard,
onUserTrackingModeChanged: @escaping (MLNUserTrackingMode, Bool) -> Void = { _, _ in },
onStyleLoaded: @escaping ((MLNStyle) -> Void),
Expand All @@ -53,6 +57,7 @@ public struct NavigationMapView: View {
self.styleURL = styleURL
_camera = camera
self.navigationState = navigationState
self.locationManagerConfiguration = locationManagerConfiguration
self.onUserTrackingModeChanged = onUserTrackingModeChanged
self.onStyleLoaded = onStyleLoaded
userLayers = makeMapContent()
Expand All @@ -64,7 +69,7 @@ public struct NavigationMapView: View {
MapView(
styleURL: styleURL,
camera: $camera,
locationManager: locationManager,
locationManager: activeLocationManager,
activity: activity
) {
// TODO: Create logic and style for route previews. Unless ferrostarCore will handle this internally.
Expand Down Expand Up @@ -112,11 +117,22 @@ public struct NavigationMapView: View {
if let userLocation = navigationState?.preferredUserLocation,
// There is no reason to push an update if the coordinate and heading are the same.
// That's all that gets displayed, so it's all that MapLibre should care about.
locationManager.lastLocation.coordinate != userLocation.coordinates
.clLocationCoordinate2D || locationManager.lastLocation.course != userLocation.clLocation.course
activeNavigatingLocationManager.lastLocation.coordinate != userLocation.coordinates.clLocationCoordinate2D ||
activeNavigatingLocationManager.lastLocation.course != userLocation.clLocation.course
{
locationManager.lastLocation = userLocation.clLocation
activeNavigatingLocationManager.lastLocation = userLocation.clLocation
}
}

private var activeNavigatingLocationManager: any NavigationDrivenLocationManager {
locationManagerConfiguration?.navigatingLocationManager ?? defaultNavigatingLocationManager
}

private var activeLocationManager: (any MLNLocationManager)? {
if navigationState?.isNavigating == true {
return activeNavigatingLocationManager
}
return locationManagerConfiguration?.nonNavigatingLocationManager
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public struct PortraitNavigationView: View {
let styleURL: URL
@Binding var camera: MapViewCamera
let navigationCamera: MapViewCamera
let locationManagerConfiguration: NavigationLocationManagerConfiguration?

private let navigationState: NavigationState?
private let userLayers: [StyleLayerDefinition]
Expand Down Expand Up @@ -47,6 +48,7 @@ public struct PortraitNavigationView: View {
camera: Binding<MapViewCamera>,
navigationCamera: MapViewCamera = .automotiveNavigation(),
navigationState: NavigationState?,
locationManagerConfiguration: NavigationLocationManagerConfiguration? = nil,
isMuted: Bool,
minimumSafeAreaInsets: EdgeInsets = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16),
onTapMute: @escaping () -> Void,
Expand All @@ -55,6 +57,7 @@ public struct PortraitNavigationView: View {
) {
self.styleURL = styleURL
self.navigationState = navigationState
self.locationManagerConfiguration = locationManagerConfiguration
self.isMuted = isMuted
self.minimumSafeAreaInsets = minimumSafeAreaInsets
self.onTapMute = onTapMute
Expand All @@ -73,6 +76,7 @@ public struct PortraitNavigationView: View {
styleURL: styleURL,
camera: $camera,
navigationState: navigationState,
locationManagerConfiguration: locationManagerConfiguration,
onUserTrackingModeChanged: { mode, _ in
userTrackingMode = mode
},
Expand Down
Loading