Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 = .default,
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,28 @@
import CoreLocation
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`:
/// - defaults to ``StaticLocationManager`` fed by Ferrostar navigation state.
public struct NavigationLocationManagerConfiguration {
public var nonNavigatingLocationManager: (any MLNLocationManager)?
public var navigatingLocationManager: any NavigationDrivenLocationManager

public init(
nonNavigatingLocationManager: (any MLNLocationManager)? = nil,
navigatingLocationManager: any NavigationDrivenLocationManager =
StaticLocationManager(initialLocation: CLLocation())
) {
self.nonNavigatingLocationManager = nonNavigatingLocationManager
self.navigatingLocationManager = navigatingLocationManager
}

public static var `default`: Self {
.init()
}
}
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 @@ -47,6 +48,7 @@ public struct DynamicallyOrientingNavigationView: View {
camera: Binding<MapViewCamera>,
navigationCamera: MapViewCamera = .automotiveNavigation(),
navigationState: NavigationState?,
locationManagerConfiguration: NavigationLocationManagerConfiguration = .default,
isMuted: Bool,
minimumSafeAreaInsets: EdgeInsets = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16),
onTapMute: @escaping () -> Void,
Expand All @@ -55,6 +57,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 @@ -75,6 +78,7 @@ public struct DynamicallyOrientingNavigationView: View {
styleURL: styleURL,
camera: $camera,
navigationState: navigationState,
locationManagerConfiguration: locationManagerConfiguration,
onStyleLoaded: { _ in
if isNavigating {
camera = navigationCamera
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 @@ -47,6 +48,7 @@ public struct LandscapeNavigationView: View {
camera: Binding<MapViewCamera>,
navigationCamera: MapViewCamera = .automotiveNavigation(),
navigationState: NavigationState?,
locationManagerConfiguration: NavigationLocationManagerConfiguration = .default,
isMuted: Bool,
minimumSafeAreaInsets: EdgeInsets = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16),
onTapMute: @escaping () -> Void,
Expand All @@ -55,6 +57,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 @@ -72,6 +75,7 @@ public struct LandscapeNavigationView: View {
styleURL: styleURL,
camera: $camera,
navigationState: navigationState,
locationManagerConfiguration: locationManagerConfiguration,
onStyleLoaded: { _ in
camera = navigationCamera
}
Expand Down
21 changes: 16 additions & 5 deletions apple/Sources/FerrostarMapLibreUI/Views/NavigationMapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ public struct NavigationMapView: View {
// TODO: Configurable camera and user "puck" rotation modes

private let navigationState: NavigationState?
private let locationManagerConfiguration: NavigationLocationManagerConfiguration

@State private var locationManager = StaticLocationManager(initialLocation: CLLocation())
@State private var navigatingLocationManager: any NavigationDrivenLocationManager

// MARK: Camera Settings

Expand All @@ -44,24 +45,27 @@ public struct NavigationMapView: View {
styleURL: URL,
camera: Binding<MapViewCamera>,
navigationState: NavigationState?,
locationManagerConfiguration: NavigationLocationManagerConfiguration = .default,
activity: MapActivity = .standard,
onStyleLoaded: @escaping ((MLNStyle) -> Void),
@MapViewContentBuilder _ makeMapContent: () -> [StyleLayerDefinition] = { [] }
) {
self.styleURL = styleURL
_camera = camera
self.navigationState = navigationState
self.locationManagerConfiguration = locationManagerConfiguration
self.onStyleLoaded = onStyleLoaded
userLayers = makeMapContent()
self.activity = activity
_navigatingLocationManager = State(initialValue: locationManagerConfiguration.navigatingLocationManager)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern is a bit confusing. You create an explicit state wrapper here with an initial value, but below on line 126 you're accessing the configuration property directly to get the non navigating location manager.

I'm not quite sure all the plumbing here is correct, basically.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch - I've changed the plumbing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool; thanks!

}

public var body: some View {
GeometryReader { geometry in
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 @@ -108,12 +112,19 @@ 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
navigatingLocationManager.lastLocation.coordinate != userLocation.coordinates
.clLocationCoordinate2D || navigatingLocationManager.lastLocation.course != userLocation.clLocation.course
{
locationManager.lastLocation = userLocation.clLocation
navigatingLocationManager.lastLocation = userLocation.clLocation
}
}

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

#Preview("Navigation Map View") {
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 @@ -46,6 +47,7 @@ public struct PortraitNavigationView: View {
camera: Binding<MapViewCamera>,
navigationCamera: MapViewCamera = .automotiveNavigation(),
navigationState: NavigationState?,
locationManagerConfiguration: NavigationLocationManagerConfiguration = .default,
isMuted: Bool,
minimumSafeAreaInsets: EdgeInsets = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16),
onTapMute: @escaping () -> Void,
Expand All @@ -54,6 +56,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 @@ -72,6 +75,7 @@ public struct PortraitNavigationView: View {
styleURL: styleURL,
camera: $camera,
navigationState: navigationState,
locationManagerConfiguration: locationManagerConfiguration,
onStyleLoaded: { _ in
camera = navigationCamera
}
Expand Down
Loading