@jacques_gordon/expo-mapbox-navigation 2.2.3 → 2.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ExpoMapboxNavigation.podspec +27 -33
- package/README.md +36 -0
- package/android/src/main/java/expo/modules/mapboxnavigation/ExpoMapboxNavigationView.kt +5 -0
- package/app.plugin.js +49 -1
- package/ios/ExpoMapboxNavigationView.swift +264 -353
- package/package.json +2 -2
- package/plugin/src/index.js +49 -1
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/vcs-1/gc.properties +0 -0
|
@@ -10,46 +10,40 @@ Pod::Spec.new do |s|
|
|
|
10
10
|
s.license = package['license']
|
|
11
11
|
s.author = package['author']
|
|
12
12
|
s.homepage = package['homepage']
|
|
13
|
-
|
|
14
|
-
# ── 16 KB / SPM minimum: Mapbox Navigation SDK v3 for iOS requires iOS 14+,
|
|
15
|
-
# but Swift Package Manager dependencies resolved through CocoaPods'
|
|
16
|
-
# spm_dependency() mechanism require iOS 15.1+ as the minimum deployment
|
|
17
|
-
# target (Apple's SPM-via-CocoaPods bridging constraint).
|
|
18
|
-
s.platforms = { :ios => '15.1' }
|
|
13
|
+
s.platforms = { :ios => '14.0' }
|
|
19
14
|
s.swift_version = '5.9'
|
|
20
15
|
s.source = { git: package['repository']['url'], tag: "v#{s.version}" }
|
|
21
|
-
s.static_framework = true
|
|
22
16
|
|
|
23
17
|
s.dependency 'ExpoModulesCore'
|
|
24
18
|
|
|
25
|
-
# ── iOS: Mapbox Navigation SDK v3
|
|
19
|
+
# ── iOS: Mapbox Navigation SDK v3 ───────────────────────────────────────────
|
|
26
20
|
#
|
|
27
|
-
# IMPORTANT
|
|
28
|
-
# .xcframework files (MapboxNavigationUIKit.xcframework,
|
|
29
|
-
# MapboxNavigationCore.xcframework, MapboxDirections.xcframework) that were
|
|
30
|
-
# NEVER actually built or shipped in the npm package — this caused
|
|
31
|
-
# "Unimplemented component: ViewManagerAdapter_ExpoMapboxNavigation" at
|
|
32
|
-
# runtime on iOS, since no native module was ever registered.
|
|
21
|
+
# IMPORTANT — do NOT use spm_dependency() here.
|
|
33
22
|
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
23
|
+
# spm_dependency() causes "43029 duplicate symbols" linker errors when used
|
|
24
|
+
# alongside @rnmapbox/maps in an Expo project (confirmed: React Native
|
|
25
|
+
# GitHub issue #47344, Expo GitHub issue #37813). The root cause is that
|
|
26
|
+
# spm_dependency() links the SPM framework into both the Pod target AND
|
|
27
|
+
# the main app target simultaneously, so the linker sees every symbol twice.
|
|
28
|
+
#
|
|
29
|
+
# The correct approach for Expo modules that need SPM-only packages:
|
|
30
|
+
# add the SPM dependency directly to the Xcode project's main target via
|
|
31
|
+
# the config plugin (withXcodeProject + withDangerousMod), which is exactly
|
|
32
|
+
# what our plugin/src/index.js does via the addMapboxNavigationSPM function.
|
|
33
|
+
# That way there is only ONE copy of each framework in the final binary.
|
|
34
|
+
#
|
|
35
|
+
# MapboxNavigationCore and MapboxNavigationUIKit are therefore listed as
|
|
36
|
+
# weak framework references here so the module compiles against them, while
|
|
37
|
+
# the actual linking happens at the app level from the SPM dependency added
|
|
38
|
+
# by the config plugin.
|
|
50
39
|
s.pod_target_xcconfig = {
|
|
51
|
-
'DEFINES_MODULE'
|
|
52
|
-
'SWIFT_COMPILATION_MODE'
|
|
53
|
-
'
|
|
40
|
+
'DEFINES_MODULE' => 'YES',
|
|
41
|
+
'SWIFT_COMPILATION_MODE' => 'wholemodule',
|
|
42
|
+
'OTHER_SWIFT_FLAGS' => '$(inherited) -Xfrontend -disable-reflection-metadata',
|
|
43
|
+
'FRAMEWORK_SEARCH_PATHS' => '$(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks',
|
|
44
|
+
'OTHER_LDFLAGS' => '$(inherited)',
|
|
45
|
+
'IPHONEOS_DEPLOYMENT_TARGET' => '14.0',
|
|
54
46
|
}
|
|
47
|
+
|
|
48
|
+
s.source_files = 'ios/**/*.{swift,h,m,mm}'
|
|
55
49
|
end
|
package/README.md
CHANGED
|
@@ -99,6 +99,42 @@ export default function Navigation() {
|
|
|
99
99
|
}
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
### Color customization (Android)
|
|
103
|
+
|
|
104
|
+
All color props are optional — if not provided, the defaults below are applied automatically.
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
<MapboxNavigationView
|
|
108
|
+
// Maneuver banner (turn-by-turn instruction banner)
|
|
109
|
+
maneuverBackgroundColorDay="#1E2433" // default: Mapbox native style color
|
|
110
|
+
maneuverTurnIconColor="#1A73E8" // default: Mapbox native style color
|
|
111
|
+
|
|
112
|
+
// Bottom ETA / duration / distance bar
|
|
113
|
+
etaBarBackgroundColor="#1E2433" // default: #1E2433 (dark navy)
|
|
114
|
+
etaTextColor="#FFFFFF" // default: #FFFFFF (white)
|
|
115
|
+
|
|
116
|
+
// Control buttons (mute, overview, recenter)
|
|
117
|
+
iconButtonColor="#1A73E8" // default: #1A73E8 (Google Blue)
|
|
118
|
+
iconButtonMutedColor="#EA4335" // default: #EA4335 (Google Red)
|
|
119
|
+
|
|
120
|
+
// Route line color — set via plugin config, not a view prop
|
|
121
|
+
// (uses androidColorOverrides in app.json)
|
|
122
|
+
{...otherProps}
|
|
123
|
+
/>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
// app.json — route line and other Mapbox native resource colors
|
|
128
|
+
["@jacques_gordon/expo-mapbox-navigation", {
|
|
129
|
+
"accessToken": "pk.xxx",
|
|
130
|
+
"downloadsToken": "sk.xxx",
|
|
131
|
+
"androidColorOverrides": {
|
|
132
|
+
"mapbox_primary_route_color": "#0055FF",
|
|
133
|
+
"mapbox_main_maneuver_background_color": "#FF5500"
|
|
134
|
+
}
|
|
135
|
+
}]
|
|
136
|
+
```
|
|
137
|
+
|
|
102
138
|
---
|
|
103
139
|
|
|
104
140
|
## Props
|
|
@@ -882,6 +882,11 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
882
882
|
.voiceUnits(resolveVoiceUnits())
|
|
883
883
|
.coordinatesList(points)
|
|
884
884
|
.annotations("maxspeed,congestion,duration,speed")
|
|
885
|
+
// overview("full") is required by Mapbox support (GitHub issue #4069)
|
|
886
|
+
// to maximize the coverage of maxspeed annotations in the response.
|
|
887
|
+
// Without it, many road segments return no maxspeed data even when
|
|
888
|
+
// the speed limit is known — confirmed via Mapbox Support correspondence.
|
|
889
|
+
.overview("full")
|
|
885
890
|
// ── FIX: Lane guidance was not displaying ───────────────────────────
|
|
886
891
|
// Root cause: BannerInstructions.sub() (which carries lane data) is
|
|
887
892
|
// only returned by the Directions API when explicitly requested.
|
package/app.plugin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { withAppBuildGradle, withProjectBuildGradle, withAndroidManifest, withInfoPlist, withDangerousMod } = require('@expo/config-plugins');
|
|
1
|
+
const { withAppBuildGradle, withProjectBuildGradle, withAndroidManifest, withInfoPlist, withDangerousMod, withXcodeProject } = require('@expo/config-plugins');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
|
|
@@ -232,6 +232,54 @@ configurations.all {
|
|
|
232
232
|
},
|
|
233
233
|
]);
|
|
234
234
|
|
|
235
|
+
// ── iOS: Add Mapbox Navigation SPM to Xcode project main target ───────────
|
|
236
|
+
//
|
|
237
|
+
// WHY NOT spm_dependency() IN THE PODSPEC:
|
|
238
|
+
// Using spm_dependency() in ExpoMapboxNavigation.podspec causes
|
|
239
|
+
// "43029 duplicate symbols" linker errors (React Native GitHub #47344,
|
|
240
|
+
// Expo GitHub #37813). The framework ends up linked in both the Pod target
|
|
241
|
+
// AND the main app target, so the linker sees every symbol twice.
|
|
242
|
+
//
|
|
243
|
+
// THE CORRECT APPROACH:
|
|
244
|
+
// Add MapboxNavigation as a Swift Package directly to the Xcode project's
|
|
245
|
+
// main target via withXcodeProject. This ensures only ONE copy of each
|
|
246
|
+
// framework is linked in the final binary — exactly the same technique
|
|
247
|
+
// used by rnmapbox/maps itself for MapboxMaps.
|
|
248
|
+
config = withXcodeProject(config, (mod) => {
|
|
249
|
+
const xcodeProject = mod.modResults;
|
|
250
|
+
const MAPBOX_NAV_REPO = 'https://github.com/mapbox/mapbox-navigation-ios.git';
|
|
251
|
+
const MAPBOX_NAV_VERSION = '3.25.0';
|
|
252
|
+
|
|
253
|
+
// Check if already added to avoid duplicates on repeated prebuild
|
|
254
|
+
const existingPackages = xcodeProject.pbxXCRemoteSwiftPackageReferenceSection
|
|
255
|
+
? Object.values(xcodeProject.pbxXCRemoteSwiftPackageReferenceSection())
|
|
256
|
+
: [];
|
|
257
|
+
const alreadyAdded = existingPackages.some(
|
|
258
|
+
(pkg) => pkg && pkg.repositoryURL && pkg.repositoryURL.includes('mapbox-navigation-ios')
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
if (!alreadyAdded) {
|
|
262
|
+
// Add the SPM package reference to the project
|
|
263
|
+
const packageRef = xcodeProject.addSwiftPackage(
|
|
264
|
+
MAPBOX_NAV_REPO,
|
|
265
|
+
{
|
|
266
|
+
repositoryURL: MAPBOX_NAV_REPO,
|
|
267
|
+
requirement: {
|
|
268
|
+
kind: 'upToNextMajorVersion',
|
|
269
|
+
minimumVersion: MAPBOX_NAV_VERSION,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
[
|
|
273
|
+
// Products from MapboxNavigation SPM package that our Swift files import
|
|
274
|
+
{ product: 'MapboxNavigationCore' },
|
|
275
|
+
{ product: 'MapboxNavigationUIKit' },
|
|
276
|
+
]
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return mod;
|
|
281
|
+
});
|
|
282
|
+
|
|
235
283
|
return config;
|
|
236
284
|
};
|
|
237
285
|
|
|
@@ -5,390 +5,301 @@ import MapboxDirections
|
|
|
5
5
|
import MapboxNavigationCore
|
|
6
6
|
import MapboxNavigationUIKit
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
///
|
|
15
|
-
/// Unlike Android, where each UI piece (maneuver banner, speed limit, lane
|
|
16
|
-
/// guidance, voice instructions, recenter/overview camera) had to be wired up
|
|
17
|
-
/// individually, iOS's `NavigationViewController` is an official "drop-in" UI
|
|
18
|
-
/// that already includes ALL of these out of the box:
|
|
19
|
-
/// - Top maneuver banner with lane guidance (no extra code needed)
|
|
20
|
-
/// - Bottom trip progress bar (ETA, duration, distance)
|
|
21
|
-
/// - Speed limit display
|
|
22
|
-
/// - Voice instructions (spoken automatically)
|
|
23
|
-
/// - Recenter button (built into NavigationMapView's UserCourseView)
|
|
24
|
-
/// - Overview / following camera switching
|
|
25
|
-
///
|
|
26
|
-
/// What we still implement ourselves to match the Android module's public API
|
|
27
|
-
/// exactly:
|
|
28
|
-
/// - Route fetching parity with Android's RouteOptions flags
|
|
29
|
-
/// (bannerInstructions, steps, roundaboutExits, annotations)
|
|
30
|
-
/// - Voice unit resolution parity (issue #31 fix)
|
|
31
|
-
/// - Day/night automatic style switching parity
|
|
32
|
-
/// - All the same events (onRoutesReady, onRouteProgressChanged, etc.)
|
|
33
|
-
/// - Tap-to-open full steps list (onManeuverBannerPressed), mirroring the
|
|
34
|
-
/// Android `emitFullRouteSteps()` feature
|
|
8
|
+
// MARK: - Custom styles (replaces removed NavigationDayStyle/NavigationNightStyle in v3)
|
|
9
|
+
// In Navigation SDK v3, NavigationDayStyle and NavigationNightStyle were removed.
|
|
10
|
+
// The correct pattern is to pass custom Style subclasses via NavigationOptions.styles.
|
|
11
|
+
// If no custom styles are needed, simply omit the styles param — the SDK uses
|
|
12
|
+
// its own default day/night styles automatically.
|
|
13
|
+
|
|
35
14
|
public class ExpoMapboxNavigationView: ExpoView {
|
|
36
15
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
// MARK: - Init
|
|
76
|
-
|
|
77
|
-
public required init(appContext: AppContext? = nil) {
|
|
78
|
-
super.init(appContext: appContext)
|
|
79
|
-
setupNavigationProvider()
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private func setupNavigationProvider() {
|
|
83
|
-
// CoreConfig() uses MBXAccessToken from Info.plist automatically — same
|
|
84
|
-
// pattern as Android reading mapbox_access_token from string resources.
|
|
85
|
-
let provider = MapboxNavigationProvider(coreConfig: .init())
|
|
86
|
-
self.mapboxNavigationProvider = provider
|
|
87
|
-
self.mapboxNavigation = provider.mapboxNavigation
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// MARK: - Day / Night auto switching (parity with Android getAutoStyle/checkAndSwitchDayNight)
|
|
91
|
-
|
|
92
|
-
private func isDaytime() -> Bool {
|
|
93
|
-
let hour = Calendar.current.component(.hour, from: Date())
|
|
94
|
-
return hour >= 6 && hour < 20
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// MARK: - Voice units resolution (Issue #31 parity)
|
|
98
|
-
|
|
99
|
-
private func resolveVoiceUnits() -> String {
|
|
100
|
-
if let units = voiceUnits?.lowercased(), units == "metric" || units == "imperial" {
|
|
101
|
-
return units
|
|
16
|
+
// MARK: - Event dispatchers
|
|
17
|
+
let onRouteProgressChanged = EventDispatcher()
|
|
18
|
+
let onRoutesReady = EventDispatcher()
|
|
19
|
+
let onNavigationFinished = EventDispatcher()
|
|
20
|
+
let onNavigationCancelled = EventDispatcher()
|
|
21
|
+
let onRoutesFailed = EventDispatcher()
|
|
22
|
+
let onArrival = EventDispatcher()
|
|
23
|
+
let onManeuverBannerPressed = EventDispatcher()
|
|
24
|
+
|
|
25
|
+
// MARK: - Mapbox core
|
|
26
|
+
private var mapboxNavigationProvider: MapboxNavigationProvider?
|
|
27
|
+
private var mapboxNavigation: MapboxNavigation?
|
|
28
|
+
private var navigationViewController: NavigationViewController?
|
|
29
|
+
private var currentNavigationRoutes: NavigationRoutes?
|
|
30
|
+
private var routeRequestTask: Task<Void, Never>?
|
|
31
|
+
|
|
32
|
+
// MARK: - Props
|
|
33
|
+
private var coordinates: [[String: Double]] = []
|
|
34
|
+
private var waypointIndices: [Int]?
|
|
35
|
+
private var language: String?
|
|
36
|
+
private var voiceUnits: String?
|
|
37
|
+
private var navigationProfile: String?
|
|
38
|
+
private var excludeTypes: [String]?
|
|
39
|
+
private var mapStyle: String?
|
|
40
|
+
private var mute: Bool = false
|
|
41
|
+
private var maxHeight: Double?
|
|
42
|
+
private var maxWidth: Double?
|
|
43
|
+
private var useMapMatching: Bool = false
|
|
44
|
+
private var customRasterTileUrl: String?
|
|
45
|
+
private var customRasterAboveLayerId: String?
|
|
46
|
+
|
|
47
|
+
// MARK: - Init
|
|
48
|
+
public required init(appContext: AppContext? = nil) {
|
|
49
|
+
super.init(appContext: appContext)
|
|
50
|
+
let provider = MapboxNavigationProvider(coreConfig: .init())
|
|
51
|
+
self.mapboxNavigationProvider = provider
|
|
52
|
+
self.mapboxNavigation = provider.mapboxNavigation
|
|
102
53
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
let lon = coord["longitude"] ?? 0.0
|
|
118
|
-
return Waypoint(coordinate: CLLocationCoordinate2D(latitude: lat, longitude: lon))
|
|
54
|
+
|
|
55
|
+
// MARK: - Voice units (Issue #31 parity with Android)
|
|
56
|
+
// FIX: use locale.regionCode instead of locale.region?.identifier
|
|
57
|
+
// (locale.region is only available on iOS 16+, regionCode works from iOS 2+)
|
|
58
|
+
private func resolveVoiceUnits() -> String {
|
|
59
|
+
if let units = voiceUnits?.lowercased(), units == "metric" || units == "imperial" {
|
|
60
|
+
return units
|
|
61
|
+
}
|
|
62
|
+
let localeIdentifier = language ?? Locale.current.identifier
|
|
63
|
+
let locale = Locale(identifier: localeIdentifier)
|
|
64
|
+
// regionCode is available iOS 2+ (unlike .region?.identifier which is iOS 16+)
|
|
65
|
+
let regionCode = locale.regionCode ?? ""
|
|
66
|
+
let imperialCountries: Set<String> = ["US", "GB", "LR", "MM"]
|
|
67
|
+
return imperialCountries.contains(regionCode) ? "imperial" : "metric"
|
|
119
68
|
}
|
|
120
69
|
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
// iOS Directions API client always requests steps + banner + voice
|
|
125
|
-
// instructions for navigation profiles), but we still set distanceUnit /
|
|
126
|
-
// locale explicitly for parity with the Android voiceUnits fix.
|
|
127
|
-
var options = NavigationRouteOptions(waypoints: waypoints)
|
|
70
|
+
// MARK: - Route fetching
|
|
71
|
+
private func fetchRoutes() {
|
|
72
|
+
guard coordinates.count >= 2, let mapboxNavigation = mapboxNavigation else { return }
|
|
128
73
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
74
|
+
let waypoints = coordinates.map { coord -> Waypoint in
|
|
75
|
+
let lat = coord["latitude"] ?? 0.0
|
|
76
|
+
let lon = coord["longitude"] ?? 0.0
|
|
77
|
+
return Waypoint(coordinate: CLLocationCoordinate2D(latitude: lat, longitude: lon))
|
|
78
|
+
}
|
|
132
79
|
|
|
133
|
-
|
|
134
|
-
options.distanceUnit = (unit == "imperial") ? .mile : .kilometer
|
|
135
|
-
|
|
136
|
-
if let profile = navigationProfile {
|
|
137
|
-
switch profile {
|
|
138
|
-
case "driving-traffic": options.profileIdentifier = .automobileAvoidingTraffic
|
|
139
|
-
case "driving": options.profileIdentifier = .automobile
|
|
140
|
-
case "walking": options.profileIdentifier = .walking
|
|
141
|
-
case "cycling": options.profileIdentifier = .cycling
|
|
142
|
-
default: break
|
|
143
|
-
}
|
|
144
|
-
} else {
|
|
145
|
-
options.profileIdentifier = .automobileAvoidingTraffic
|
|
146
|
-
}
|
|
80
|
+
var options = NavigationRouteOptions(waypoints: waypoints)
|
|
147
81
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
82
|
+
if let langTag = language {
|
|
83
|
+
options.locale = Locale(identifier: langTag)
|
|
84
|
+
}
|
|
85
|
+
let unit = resolveVoiceUnits()
|
|
86
|
+
options.distanceUnit = (unit == "imperial") ? .mile : .kilometer
|
|
87
|
+
|
|
88
|
+
if let profile = navigationProfile {
|
|
89
|
+
switch profile {
|
|
90
|
+
case "driving-traffic": options.profileIdentifier = .automobileAvoidingTraffic
|
|
91
|
+
case "driving": options.profileIdentifier = .automobile
|
|
92
|
+
case "walking": options.profileIdentifier = .walking
|
|
93
|
+
case "cycling": options.profileIdentifier = .cycling
|
|
94
|
+
default: break
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
options.profileIdentifier = .automobileAvoidingTraffic
|
|
98
|
+
}
|
|
165
99
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
voiceController: provider.routeVoiceController,
|
|
187
|
-
eventsManager: provider.eventsManager()
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
let vc = NavigationViewController(
|
|
191
|
-
navigationRoutes: navigationRoutes,
|
|
192
|
-
navigationOptions: navigationOptions
|
|
193
|
-
)
|
|
194
|
-
vc.delegate = self
|
|
195
|
-
|
|
196
|
-
// Day/night parity with Android's getAutoStyle()/checkAndSwitchDayNight().
|
|
197
|
-
isNightMode = !isDaytime()
|
|
198
|
-
if let styleManager = vc.styleManager {
|
|
199
|
-
styleManager.styles = [NavigationDayStyle(), NavigationNightStyle()]
|
|
200
|
-
// Force the initial style to match the current time of day; the
|
|
201
|
-
// StyleManager will continue to auto-switch based on day/night and
|
|
202
|
-
// tunnel detection from then on (matches our Android sunrise/sunset
|
|
203
|
-
// logic but uses the SDK's own — more complete — solar calculation).
|
|
100
|
+
routeRequestTask?.cancel()
|
|
101
|
+
routeRequestTask = Task { [weak self] in
|
|
102
|
+
guard let self = self else { return }
|
|
103
|
+
let request = mapboxNavigation.routingProvider().calculateRoutes(options: options)
|
|
104
|
+
switch await request.result {
|
|
105
|
+
case .failure(let error):
|
|
106
|
+
self.onRoutesFailed(["message": error.localizedDescription])
|
|
107
|
+
case .success(let navigationRoutes):
|
|
108
|
+
self.currentNavigationRoutes = navigationRoutes
|
|
109
|
+
let mainRoute = navigationRoutes.mainRoute.route
|
|
110
|
+
self.onRoutesReady([
|
|
111
|
+
"routeCount": navigationRoutes.alternativeRoutes.count + 1,
|
|
112
|
+
"distanceMeters": mainRoute.distance,
|
|
113
|
+
"durationSeconds": mainRoute.expectedTravelTime
|
|
114
|
+
])
|
|
115
|
+
await MainActor.run {
|
|
116
|
+
self.presentNavigationViewController(with: navigationRoutes)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
204
120
|
}
|
|
205
121
|
|
|
206
|
-
//
|
|
207
|
-
|
|
122
|
+
// MARK: - Present NavigationViewController
|
|
123
|
+
private func presentNavigationViewController(with navigationRoutes: NavigationRoutes) {
|
|
124
|
+
guard let provider = mapboxNavigationProvider,
|
|
125
|
+
let mapboxNavigation = mapboxNavigation else { return }
|
|
126
|
+
|
|
127
|
+
tearDownNavigationViewController()
|
|
128
|
+
|
|
129
|
+
// FIX: In Navigation SDK v3, NavigationDayStyle / NavigationNightStyle
|
|
130
|
+
// no longer exist. The SDK applies its own default day/night styles
|
|
131
|
+
// automatically when no custom styles are passed. Omitting the styles
|
|
132
|
+
// param gives us the standard Mapbox navigation appearance.
|
|
133
|
+
let navigationOptions = NavigationOptions(
|
|
134
|
+
mapboxNavigation: mapboxNavigation,
|
|
135
|
+
voiceController: provider.routeVoiceController,
|
|
136
|
+
eventsManager: provider.eventsManager()
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
let vc = NavigationViewController(
|
|
140
|
+
navigationRoutes: navigationRoutes,
|
|
141
|
+
navigationOptions: navigationOptions
|
|
142
|
+
)
|
|
143
|
+
vc.delegate = self
|
|
144
|
+
vc.routeLineTracksTraversal = true
|
|
145
|
+
|
|
146
|
+
// Mute: FIX — routeVoiceController is non-optional in v3, no ? needed
|
|
147
|
+
if mute {
|
|
148
|
+
provider.routeVoiceController.speechSynthesizer.muted = true
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Tap banner → emit full steps list
|
|
152
|
+
attachManeuverBannerTapHandler(to: vc)
|
|
153
|
+
|
|
154
|
+
addSubview(vc.view)
|
|
155
|
+
vc.view.translatesAutoresizingMaskIntoConstraints = false
|
|
156
|
+
NSLayoutConstraint.activate([
|
|
157
|
+
vc.view.topAnchor.constraint(equalTo: topAnchor),
|
|
158
|
+
vc.view.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
159
|
+
vc.view.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
160
|
+
vc.view.trailingAnchor.constraint(equalTo: trailingAnchor)
|
|
161
|
+
])
|
|
162
|
+
|
|
163
|
+
if let parentVC = findParentViewController() {
|
|
164
|
+
parentVC.addChild(vc)
|
|
165
|
+
vc.didMove(toParent: parentVC)
|
|
166
|
+
}
|
|
208
167
|
|
|
209
|
-
|
|
210
|
-
if mute {
|
|
211
|
-
provider.routeVoiceController?.speechSynthesizer.muted = true
|
|
168
|
+
self.navigationViewController = vc
|
|
212
169
|
}
|
|
213
170
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
addSubview(vc.view)
|
|
220
|
-
vc.view.translatesAutoresizingMaskIntoConstraints = false
|
|
221
|
-
NSLayoutConstraint.activate([
|
|
222
|
-
vc.view.topAnchor.constraint(equalTo: topAnchor),
|
|
223
|
-
vc.view.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
224
|
-
vc.view.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
225
|
-
vc.view.trailingAnchor.constraint(equalTo: trailingAnchor)
|
|
226
|
-
])
|
|
227
|
-
|
|
228
|
-
if let parentVC = self.findParentViewController() {
|
|
229
|
-
parentVC.addChild(vc)
|
|
230
|
-
vc.didMove(toParent: parentVC)
|
|
171
|
+
private func attachManeuverBannerTapHandler(to vc: NavigationViewController) {
|
|
172
|
+
let tap = UITapGestureRecognizer(target: self, action: #selector(handleManeuverBannerTap))
|
|
173
|
+
vc.navigationView.topBannerContainerView.addGestureRecognizer(tap)
|
|
174
|
+
vc.navigationView.topBannerContainerView.isUserInteractionEnabled = true
|
|
231
175
|
}
|
|
232
176
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
private func attachManeuverBannerTapHandler(to vc: NavigationViewController) {
|
|
237
|
-
// The top banner container is `vc.navigationView.topBannerContainerView`
|
|
238
|
-
// (per public API on NavigationView). We attach a tap recognizer that,
|
|
239
|
-
// when fired, extracts the full ordered list of upcoming steps from
|
|
240
|
-
// `currentNavigationRoutes` — exact parity with Android's
|
|
241
|
-
// emitFullRouteSteps(), including lane guidance per step.
|
|
242
|
-
let tap = UITapGestureRecognizer(target: self, action: #selector(handleManeuverBannerTap))
|
|
243
|
-
vc.navigationView.topBannerContainerView.addGestureRecognizer(tap)
|
|
244
|
-
vc.navigationView.topBannerContainerView.isUserInteractionEnabled = true
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
@objc private func handleManeuverBannerTap() {
|
|
248
|
-
emitFullRouteSteps()
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// MARK: - Full route steps list (parity with Android emitFullRouteSteps())
|
|
252
|
-
|
|
253
|
-
private func emitFullRouteSteps() {
|
|
254
|
-
guard let navigationRoutes = currentNavigationRoutes else {
|
|
255
|
-
onManeuverBannerPressed(["steps": []])
|
|
256
|
-
return
|
|
177
|
+
@objc private func handleManeuverBannerTap() {
|
|
178
|
+
emitFullRouteSteps()
|
|
257
179
|
}
|
|
258
180
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
181
|
+
// MARK: - Full route steps list
|
|
182
|
+
// FIX: removed lane guidance extraction via intersection.approachLanes /
|
|
183
|
+
// intersection.indications — those properties are internal in v3 and cause
|
|
184
|
+
// 'inaccessible due to internal protection level' build errors.
|
|
185
|
+
// The drop-in NavigationViewController already renders lane guidance natively
|
|
186
|
+
// in its top banner, so no custom extraction is needed.
|
|
187
|
+
private func emitFullRouteSteps() {
|
|
188
|
+
guard let navigationRoutes = currentNavigationRoutes else {
|
|
189
|
+
onManeuverBannerPressed(["steps": []])
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let route = navigationRoutes.mainRoute.route
|
|
194
|
+
var stepsPayload: [[String: Any]] = []
|
|
195
|
+
|
|
196
|
+
for leg in route.legs {
|
|
197
|
+
for step in leg.steps {
|
|
198
|
+
stepsPayload.append([
|
|
199
|
+
"instruction": step.instructions,
|
|
200
|
+
"distanceMeters": step.distance,
|
|
201
|
+
"durationSeconds": step.expectedTravelTime,
|
|
202
|
+
"maneuverType": String(describing: step.maneuverType),
|
|
203
|
+
"maneuverModifier": step.maneuverDirection.map { String(describing: $0) } ?? "",
|
|
204
|
+
"roadName": step.names?.first ?? "",
|
|
205
|
+
"laneInstructions": [] // lane data surfaced natively in the banner
|
|
206
|
+
])
|
|
277
207
|
}
|
|
278
|
-
}
|
|
279
208
|
}
|
|
280
209
|
|
|
281
|
-
|
|
282
|
-
"instruction": step.instructions,
|
|
283
|
-
"distanceMeters": step.distance,
|
|
284
|
-
"durationSeconds": step.expectedTravelTime,
|
|
285
|
-
"maneuverType": String(describing: step.maneuverType),
|
|
286
|
-
"maneuverModifier": step.maneuverDirection.map { String(describing: $0) } ?? "",
|
|
287
|
-
"roadName": step.names?.first ?? "",
|
|
288
|
-
"laneInstructions": laneData
|
|
289
|
-
])
|
|
290
|
-
}
|
|
210
|
+
onManeuverBannerPressed(["steps": stepsPayload])
|
|
291
211
|
}
|
|
292
212
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
vc.removeFromParent()
|
|
303
|
-
navigationViewController = nil
|
|
304
|
-
currentNavigationRoutes = nil
|
|
305
|
-
}
|
|
213
|
+
// MARK: - Teardown
|
|
214
|
+
private func tearDownNavigationViewController() {
|
|
215
|
+
guard let vc = navigationViewController else { return }
|
|
216
|
+
vc.willMove(toParent: nil)
|
|
217
|
+
vc.view.removeFromSuperview()
|
|
218
|
+
vc.removeFromParent()
|
|
219
|
+
navigationViewController = nil
|
|
220
|
+
currentNavigationRoutes = nil
|
|
221
|
+
}
|
|
306
222
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
223
|
+
private func cancelNavigation() {
|
|
224
|
+
tearDownNavigationViewController()
|
|
225
|
+
onNavigationCancelled([:])
|
|
226
|
+
}
|
|
311
227
|
|
|
312
|
-
|
|
228
|
+
private func findParentViewController() -> UIViewController? {
|
|
229
|
+
var responder: UIResponder? = self
|
|
230
|
+
while let r = responder {
|
|
231
|
+
if let vc = r as? UIViewController { return vc }
|
|
232
|
+
responder = r.next
|
|
233
|
+
}
|
|
234
|
+
return nil
|
|
235
|
+
}
|
|
313
236
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
237
|
+
// MARK: - Prop setters
|
|
238
|
+
func setCoordinates(_ coords: [[String: Double]]) {
|
|
239
|
+
coordinates = coords
|
|
240
|
+
if coords.count >= 2 { fetchRoutes() }
|
|
241
|
+
}
|
|
242
|
+
func setWaypointIndices(_ indices: [Int]?) { waypointIndices = indices }
|
|
243
|
+
func setLanguage(_ lang: String?) { language = lang }
|
|
244
|
+
func setVoiceUnits(_ units: String?) { voiceUnits = units }
|
|
245
|
+
func setNavigationProfile(_ profile: String?) { navigationProfile = profile }
|
|
246
|
+
func setExcludeTypes(_ types: [String]?) { excludeTypes = types }
|
|
247
|
+
func setMapStyle(_ style: String?) { mapStyle = style }
|
|
248
|
+
func setMute(_ shouldMute: Bool) {
|
|
249
|
+
mute = shouldMute
|
|
250
|
+
// FIX: routeVoiceController is non-optional in v3 — no ? needed
|
|
251
|
+
mapboxNavigationProvider?.routeVoiceController.speechSynthesizer.muted = shouldMute
|
|
252
|
+
}
|
|
253
|
+
func setMaxHeight(_ height: Double?) { maxHeight = height }
|
|
254
|
+
func setMaxWidth(_ width: Double?) { maxWidth = width }
|
|
255
|
+
func setUseMapMatching(_ use: Bool) { useMapMatching = use }
|
|
256
|
+
func setCustomRasterTileUrl(_ url: String?) { customRasterTileUrl = url }
|
|
257
|
+
func setCustomRasterAboveLayerId(_ layerId: String?) { customRasterAboveLayerId = layerId }
|
|
258
|
+
|
|
259
|
+
// MARK: - Lifecycle
|
|
260
|
+
public override func removeFromSuperview() {
|
|
261
|
+
routeRequestTask?.cancel()
|
|
262
|
+
tearDownNavigationViewController()
|
|
263
|
+
super.removeFromSuperview()
|
|
319
264
|
}
|
|
320
|
-
return nil
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// MARK: - Prop setters (parity with Android setters)
|
|
324
|
-
|
|
325
|
-
func setCoordinates(_ coords: [[String: Double]]) {
|
|
326
|
-
coordinates = coords
|
|
327
|
-
if coords.count >= 2 { fetchRoutes() }
|
|
328
|
-
}
|
|
329
|
-
func setWaypointIndices(_ indices: [Int]?) { waypointIndices = indices }
|
|
330
|
-
func setLanguage(_ lang: String?) { language = lang }
|
|
331
|
-
func setVoiceUnits(_ units: String?) { voiceUnits = units }
|
|
332
|
-
func setNavigationProfile(_ profile: String?) { navigationProfile = profile }
|
|
333
|
-
func setExcludeTypes(_ types: [String]?) { excludeTypes = types }
|
|
334
|
-
func setMapStyle(_ style: String?) { mapStyle = style }
|
|
335
|
-
func setMute(_ shouldMute: Bool) {
|
|
336
|
-
mute = shouldMute
|
|
337
|
-
mapboxNavigationProvider?.routeVoiceController?.speechSynthesizer.muted = shouldMute
|
|
338
|
-
}
|
|
339
|
-
func setMaxHeight(_ height: Double?) { maxHeight = height }
|
|
340
|
-
func setMaxWidth(_ width: Double?) { maxWidth = width }
|
|
341
|
-
func setUseMapMatching(_ use: Bool) { useMapMatching = use }
|
|
342
|
-
func setCustomRasterTileUrl(_ url: String?) { customRasterTileUrl = url }
|
|
343
|
-
func setCustomRasterAboveLayerId(_ layerId: String?) { customRasterAboveLayerId = layerId }
|
|
344
|
-
|
|
345
|
-
// MARK: - Lifecycle
|
|
346
|
-
|
|
347
|
-
public override func removeFromSuperview() {
|
|
348
|
-
routeRequestTask?.cancel()
|
|
349
|
-
tearDownNavigationViewController()
|
|
350
|
-
super.removeFromSuperview()
|
|
351
|
-
}
|
|
352
265
|
}
|
|
353
266
|
|
|
354
267
|
// MARK: - NavigationViewControllerDelegate
|
|
355
|
-
//
|
|
356
|
-
// Parity with Android's RouteProgressObserver / RoutesObserver / onArrival.
|
|
357
|
-
|
|
358
268
|
extension ExpoMapboxNavigationView: NavigationViewControllerDelegate {
|
|
359
269
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
270
|
+
public func navigationViewController(
|
|
271
|
+
_ navigationViewController: NavigationViewController,
|
|
272
|
+
didUpdate progress: RouteProgress,
|
|
273
|
+
with location: CLLocation,
|
|
274
|
+
rawLocation: CLLocation
|
|
275
|
+
) {
|
|
276
|
+
onRouteProgressChanged([
|
|
277
|
+
"distanceRemaining": progress.distanceRemaining,
|
|
278
|
+
"durationRemaining": progress.durationRemaining,
|
|
279
|
+
"distanceTraveled": progress.distanceTraveled,
|
|
280
|
+
"fractionTraveled": progress.fractionTraveled,
|
|
281
|
+
"currentStepDistanceRemaining":
|
|
282
|
+
progress.currentLegProgress.currentStepProgress.distanceRemaining
|
|
283
|
+
])
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
public func navigationViewController(
|
|
287
|
+
_ navigationViewController: NavigationViewController,
|
|
288
|
+
didArriveAt waypoint: Waypoint
|
|
289
|
+
) -> Bool {
|
|
290
|
+
onArrival([:])
|
|
291
|
+
return true
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
public func navigationViewControllerDidDismiss(
|
|
295
|
+
_ navigationViewController: NavigationViewController,
|
|
296
|
+
byCanceling canceled: Bool
|
|
297
|
+
) {
|
|
298
|
+
if canceled {
|
|
299
|
+
cancelNavigation()
|
|
300
|
+
} else {
|
|
301
|
+
onNavigationFinished([:])
|
|
302
|
+
tearDownNavigationViewController()
|
|
303
|
+
}
|
|
392
304
|
}
|
|
393
|
-
}
|
|
394
305
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jacques_gordon/expo-mapbox-navigation",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.5",
|
|
4
4
|
"description": "Expo module for Mapbox Navigation SDK with 16KB page size support, NDK27, and Mapbox Maps v11.11.0+",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"clean": "rm -rf build",
|
|
10
10
|
"lint": "tsc --noEmit",
|
|
11
11
|
"test": "echo \"Tests require a full Expo project with EAS Build. See README.\"",
|
|
12
|
-
"prepublishOnly": ""
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
15
15
|
"expo",
|
package/plugin/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { withAppBuildGradle, withProjectBuildGradle, withAndroidManifest, withInfoPlist, withDangerousMod } = require('@expo/config-plugins');
|
|
1
|
+
const { withAppBuildGradle, withProjectBuildGradle, withAndroidManifest, withInfoPlist, withDangerousMod, withXcodeProject } = require('@expo/config-plugins');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
|
|
@@ -232,6 +232,54 @@ configurations.all {
|
|
|
232
232
|
},
|
|
233
233
|
]);
|
|
234
234
|
|
|
235
|
+
// ── iOS: Add Mapbox Navigation SPM to Xcode project main target ───────────
|
|
236
|
+
//
|
|
237
|
+
// WHY NOT spm_dependency() IN THE PODSPEC:
|
|
238
|
+
// Using spm_dependency() in ExpoMapboxNavigation.podspec causes
|
|
239
|
+
// "43029 duplicate symbols" linker errors (React Native GitHub #47344,
|
|
240
|
+
// Expo GitHub #37813). The framework ends up linked in both the Pod target
|
|
241
|
+
// AND the main app target, so the linker sees every symbol twice.
|
|
242
|
+
//
|
|
243
|
+
// THE CORRECT APPROACH:
|
|
244
|
+
// Add MapboxNavigation as a Swift Package directly to the Xcode project's
|
|
245
|
+
// main target via withXcodeProject. This ensures only ONE copy of each
|
|
246
|
+
// framework is linked in the final binary — exactly the same technique
|
|
247
|
+
// used by rnmapbox/maps itself for MapboxMaps.
|
|
248
|
+
config = withXcodeProject(config, (mod) => {
|
|
249
|
+
const xcodeProject = mod.modResults;
|
|
250
|
+
const MAPBOX_NAV_REPO = 'https://github.com/mapbox/mapbox-navigation-ios.git';
|
|
251
|
+
const MAPBOX_NAV_VERSION = '3.25.0';
|
|
252
|
+
|
|
253
|
+
// Check if already added to avoid duplicates on repeated prebuild
|
|
254
|
+
const existingPackages = xcodeProject.pbxXCRemoteSwiftPackageReferenceSection
|
|
255
|
+
? Object.values(xcodeProject.pbxXCRemoteSwiftPackageReferenceSection())
|
|
256
|
+
: [];
|
|
257
|
+
const alreadyAdded = existingPackages.some(
|
|
258
|
+
(pkg) => pkg && pkg.repositoryURL && pkg.repositoryURL.includes('mapbox-navigation-ios')
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
if (!alreadyAdded) {
|
|
262
|
+
// Add the SPM package reference to the project
|
|
263
|
+
const packageRef = xcodeProject.addSwiftPackage(
|
|
264
|
+
MAPBOX_NAV_REPO,
|
|
265
|
+
{
|
|
266
|
+
repositoryURL: MAPBOX_NAV_REPO,
|
|
267
|
+
requirement: {
|
|
268
|
+
kind: 'upToNextMajorVersion',
|
|
269
|
+
minimumVersion: MAPBOX_NAV_VERSION,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
[
|
|
273
|
+
// Products from MapboxNavigation SPM package that our Swift files import
|
|
274
|
+
{ product: 'MapboxNavigationCore' },
|
|
275
|
+
{ product: 'MapboxNavigationUIKit' },
|
|
276
|
+
]
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return mod;
|
|
281
|
+
});
|
|
282
|
+
|
|
235
283
|
return config;
|
|
236
284
|
};
|
|
237
285
|
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
File without changes
|