Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
13 changes: 2 additions & 11 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

107 changes: 57 additions & 50 deletions apple/DemoApp/Demo/DemoNavigationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,58 +88,15 @@ struct DemoNavigationView: View {
Text("Loading route...")
}
}
},
bottomTrailing: {
VStack {
Text(locationLabel)
.font(.caption)
.padding(.all, 8)
.foregroundColor(.white)
.background(Color.black.opacity(0.7).clipShape(.buttonBorder, style: FillStyle()))

if locationServicesEnabled {
if model.appState.showStateButton {
NavigationUIButton {
switch model.appState {
case .idle:
showSearch = true
case let .destination(coordinate):
Task {
isFetchingRoutes = true
await model.loadRoute(coordinate)
isFetchingRoutes = false
}
case let .routes(routes):
model.selectRoute(from: routes)
case let .selectedRoute(route):
startNavigation(route)
case .navigating:
// Should not reach this.
break
}
} label: {
Text(model.appState.buttonText)
.lineLimit(1)
.minimumScaleFactor(0.5)
.font(.body.bold())
}
}
} else {
NavigationUIButton {
// TODO: enable location services.
} label: {
Text("Enable Location Services")
}
}
Button {
model.toggleLocationSimulation()
} label: {
model.locationProvider.type.label
}
.buttonStyle(NavigationUIButtonStyle())
}
}
)
.overlay(alignment: .bottomTrailing) {
if model.appState.showStateButton {
browseControls(locationServicesEnabled: locationServicesEnabled)
.padding(.trailing, 16)
.padding(.bottom, 16)
}
}

if showSearch {
model.searchView
Expand All @@ -162,6 +119,56 @@ struct DemoNavigationView: View {
allowAutoLock()
}

func browseControls(locationServicesEnabled: Bool) -> some View {
VStack {
Text(locationLabel)
.font(.caption)
.padding(.all, 8)
.foregroundColor(.white)
.background(Color.black.opacity(0.7).clipShape(.buttonBorder, style: FillStyle()))

if locationServicesEnabled {
NavigationUIButton {
switch model.appState {
case .idle:
showSearch = true
case let .destination(coordinate):
Task {
isFetchingRoutes = true
await model.loadRoute(coordinate)
isFetchingRoutes = false
}
case let .routes(routes):
model.selectRoute(from: routes)
case let .selectedRoute(route):
startNavigation(route)
case .navigating:
// Should not reach this.
break
}
} label: {
Text(model.appState.buttonText)
.lineLimit(1)
.minimumScaleFactor(0.5)
.font(.body.bold())
}
} else {
NavigationUIButton {
// TODO: enable location services.
} label: {
Text("Enable Location Services")
}
}

Button {
model.toggleLocationSimulation()
} label: {
model.locationProvider.type.label
}
.buttonStyle(NavigationUIButtonStyle())
}
}

var locationLabel: String {
guard let horizontalAccuracy = model.horizontalAccuracy else {
return "Not Authorized"
Expand Down
6 changes: 3 additions & 3 deletions apple/DemoApp/Ferrostar Demo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@
mainGroup = E9505FAE2AD449700016BF0A;
packageReferences = (
FE0519072E2051EF0084240B /* XCRemoteSwiftPackageReference "swiftui-autocomplete-search" */,
16731E072E380B9500B3E2C9 /* XCLocalSwiftPackageReference "../../../ferrostar" */,
16731E072E380B9500B3E2C9 /* XCLocalSwiftPackageReference "../.." */,
);
productRefGroup = E9505FB82AD449700016BF0A /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -640,9 +640,9 @@
/* End XCConfigurationList section */

/* Begin XCLocalSwiftPackageReference section */
16731E072E380B9500B3E2C9 /* XCLocalSwiftPackageReference "../../../ferrostar" */ = {
16731E072E380B9500B3E2C9 /* XCLocalSwiftPackageReference "../.." */ = {
isa = XCLocalSwiftPackageReference;
relativePath = ../../../ferrostar;
relativePath = ../..;
};
/* End XCLocalSwiftPackageReference section */

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 62 additions & 0 deletions apple/Sources/FerrostarMapLibreUI/Extensions/MapViewCamera.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
import Foundation
import MapLibreSwiftUI

public enum NavigationActivity {
case automotive
case bicycle
case pedestrian

var zoom: Double {
switch self {
case .automotive:
16.0
case .bicycle:
18.0
case .pedestrian:
20.0
}
}

var pitch: Double {
switch self {
case .automotive, .bicycle:
45.0
case .pedestrian:
10.0
}
}
}

public extension MapViewCamera {
/// Is the camera currently tracking (navigating)
var isTrackingUserLocationWithCourse: Bool {
Expand All @@ -10,6 +36,18 @@ public extension MapViewCamera {
return false
}

/// The default camera for navigation based on activity type.
///
/// - Parameter activity: The navigation activity profile.
/// - Returns: The configured MapViewCamera
static func navigation(activity: NavigationActivity = .automotive) -> MapViewCamera {
MapViewCamera.trackUserLocationWithCourse(
zoom: activity.zoom,
pitch: activity.pitch,
pitchRange: .fixed(activity.pitch)
)
}

/// The default camera for automotive navigation.
///
/// - Parameters:
Expand All @@ -21,4 +59,28 @@ public extension MapViewCamera {
pitch: pitch,
pitchRange: .fixed(pitch))
}

/// The default camera for bicycle navigation.
///
/// - Parameters:
/// - zoom: The zoom value (default is 18.0)
/// - pitch: The pitch (default is 45.0)
/// - Returns: The configured MapViewCamera
static func bicycleNavigation(zoom: Double = 18.0, pitch: Double = 45.0) -> MapViewCamera {
MapViewCamera.trackUserLocationWithCourse(zoom: zoom,
pitch: pitch,
pitchRange: .fixed(pitch))
}

/// The default camera for pedestrian navigation.
///
/// - Parameters:
/// - zoom: The zoom value (default is 20.0)
/// - pitch: The pitch (default is 10.0)
/// - Returns: The configured MapViewCamera
static func pedestrianNavigation(zoom: Double = 20.0, pitch: Double = 10.0) -> MapViewCamera {
MapViewCamera.trackUserLocationWithCourse(zoom: zoom,
pitch: pitch,
pitchRange: .fixed(pitch))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,17 @@ public struct DynamicallyOrientingNavigationView: View {

public var body: some View {
GeometryReader { geometry in
let isNavigating = navigationState?.isNavigating == true

ZStack {
NavigationMapView(
styleURL: styleURL,
camera: $camera,
navigationState: navigationState,
onStyleLoaded: { _ in
camera = navigationCamera
if isNavigating {
camera = navigationCamera
}
}
) {
userLayers
Expand All @@ -88,16 +92,10 @@ public struct DynamicallyOrientingNavigationView: View {
isMuted: isMuted,
showMute: navigationState?.isNavigating == true,
onMute: onTapMute,
showZoom: true,
showZoom: isNavigating,
onZoomIn: { camera.incrementZoom(by: 1) },
onZoomOut: { camera.incrementZoom(by: -1) },
cameraControlState: camera.isTrackingUserLocationWithCourse ? .showRouteOverview {
if let overviewCamera = navigationState?.routeOverviewCamera {
camera = overviewCamera
}
} : .showRecenter { // TODO: Third case when not navigating!
camera = navigationCamera
},
cameraControlState: cameraControlState,
onTapExit: onTapExit
)
.navigationViewInnerGrid {
Expand All @@ -119,16 +117,10 @@ public struct DynamicallyOrientingNavigationView: View {
isMuted: isMuted,
showMute: navigationState?.isNavigating == true,
onMute: onTapMute,
showZoom: true,
showZoom: isNavigating,
onZoomIn: { camera.incrementZoom(by: 1) },
onZoomOut: { camera.incrementZoom(by: -1) },
cameraControlState: camera.isTrackingUserLocationWithCourse ? .showRouteOverview {
if let overviewCamera = navigationState?.routeOverviewCamera {
camera = overviewCamera
}
} : .showRecenter { // TODO: Third case when not navigating!
camera = navigationCamera
},
cameraControlState: cameraControlState,
onTapExit: onTapExit
)
.navigationViewInnerGrid {
Expand All @@ -146,6 +138,23 @@ public struct DynamicallyOrientingNavigationView: View {
}
}
}

private var cameraControlState: CameraControlState {
if navigationState?.isNavigating != true {
return .hidden
}
if camera.isTrackingUserLocationWithCourse {
guard let overviewCamera = navigationState?.routeOverviewCamera else {
return .hidden
}
return .showRouteOverview {
camera = overviewCamera
}
}
return .showRecenter {
camera = navigationCamera
}
}
}

#Preview("Portrait Navigation View (Imperial)") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,7 @@ public struct LandscapeNavigationView: View {
showZoom: true,
onZoomIn: { camera.incrementZoom(by: 1) },
onZoomOut: { camera.incrementZoom(by: -1) },
cameraControlState: camera.isTrackingUserLocationWithCourse ? CameraControlState.showRecenter {
// TODO:
} : .showRecenter {
camera = navigationCamera
},
cameraControlState: cameraControlState,
onTapExit: onTapExit
)
.navigationViewInnerGrid {
Expand All @@ -111,6 +107,23 @@ public struct LandscapeNavigationView: View {
}
}
}

private var cameraControlState: CameraControlState {
if navigationState?.isNavigating != true {
return .hidden
}
if camera.isTrackingUserLocationWithCourse {
guard let overviewCamera = navigationState?.routeOverviewCamera else {
return .hidden
}
return .showRouteOverview {
camera = overviewCamera
}
}
return .showRecenter {
camera = navigationCamera
}
}
}

@available(iOS 17, *)
Expand Down
Loading
Loading