diff --git a/.circleci/config.yml b/.circleci/config.yml
index f08531727..69cc7b43f 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -84,18 +84,15 @@ jobs:
carthage bootstrap --platform all --use-netrc --use-xcframeworks
example-app-build:
macos:
- xcode: "12.4.0"
+ xcode: "13.3.0"
environment:
HOMEBREW_NO_AUTO_UPDATE: 1
steps:
- checkout
- install-mapbox-token
- - install-carthage
- - restore-cache
- - carthage-bootstrap
- run:
name: "Build example app"
- command: xcodebuild -sdk iphonesimulator -project MapboxDirections.xcodeproj -scheme 'Example' -destination 'platform=iOS Simulator,OS=14.4,name=iPhone 12 Pro Max' clean build
+ command: xcodebuild -sdk iphonesimulator -project DirectionsPlayground/DirectionsPlayground.xcodeproj -scheme 'DirectionsPlayground' -destination 'platform=iOS Simulator,OS=15.4,name=iPhone 13 Pro Max' clean build
- save-cache
build-job:
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 000000000..76bd59b7e
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,22 @@
+{
+ "configurations": [
+ {
+ "type": "lldb",
+ "request": "launch",
+ "name": "Debug mapbox-directions-swift",
+ "program": "${workspaceFolder:mapbox-directions-swift}/.build/debug/mapbox-directions-swift",
+ "args": [],
+ "cwd": "${workspaceFolder:mapbox-directions-swift}",
+ "preLaunchTask": "swift: Build Debug mapbox-directions-swift"
+ },
+ {
+ "type": "lldb",
+ "request": "launch",
+ "name": "Release mapbox-directions-swift",
+ "program": "${workspaceFolder:mapbox-directions-swift}/.build/release/mapbox-directions-swift",
+ "args": [],
+ "cwd": "${workspaceFolder:mapbox-directions-swift}",
+ "preLaunchTask": "swift: Build Release mapbox-directions-swift"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Directions Example/AppDelegate.swift b/Directions Example/AppDelegate.swift
deleted file mode 100644
index f358f5e3e..000000000
--- a/Directions Example/AppDelegate.swift
+++ /dev/null
@@ -1,10 +0,0 @@
-import SwiftUI
-
-@main
-struct TestApp: App {
- var body: some Scene {
- WindowGroup {
- ContentView(vm: DirectionsViewModel())
- }
- }
-}
diff --git a/Directions Example/Assets.xcassets/AppIcon.appiconset/Contents.json b/Directions Example/Assets.xcassets/AppIcon.appiconset/Contents.json
deleted file mode 100644
index d8db8d65f..000000000
--- a/Directions Example/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ /dev/null
@@ -1,98 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "iphone",
- "size" : "20x20",
- "scale" : "2x"
- },
- {
- "idiom" : "iphone",
- "size" : "20x20",
- "scale" : "3x"
- },
- {
- "idiom" : "iphone",
- "size" : "29x29",
- "scale" : "2x"
- },
- {
- "idiom" : "iphone",
- "size" : "29x29",
- "scale" : "3x"
- },
- {
- "idiom" : "iphone",
- "size" : "40x40",
- "scale" : "2x"
- },
- {
- "idiom" : "iphone",
- "size" : "40x40",
- "scale" : "3x"
- },
- {
- "idiom" : "iphone",
- "size" : "60x60",
- "scale" : "2x"
- },
- {
- "idiom" : "iphone",
- "size" : "60x60",
- "scale" : "3x"
- },
- {
- "idiom" : "ipad",
- "size" : "20x20",
- "scale" : "1x"
- },
- {
- "idiom" : "ipad",
- "size" : "20x20",
- "scale" : "2x"
- },
- {
- "idiom" : "ipad",
- "size" : "29x29",
- "scale" : "1x"
- },
- {
- "idiom" : "ipad",
- "size" : "29x29",
- "scale" : "2x"
- },
- {
- "idiom" : "ipad",
- "size" : "40x40",
- "scale" : "1x"
- },
- {
- "idiom" : "ipad",
- "size" : "40x40",
- "scale" : "2x"
- },
- {
- "idiom" : "ipad",
- "size" : "76x76",
- "scale" : "1x"
- },
- {
- "idiom" : "ipad",
- "size" : "76x76",
- "scale" : "2x"
- },
- {
- "idiom" : "ipad",
- "size" : "83.5x83.5",
- "scale" : "2x"
- },
- {
- "idiom" : "ios-marketing",
- "size" : "1024x1024",
- "scale" : "1x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Directions Example/Info.plist b/Directions Example/Info.plist
deleted file mode 100644
index f11db4044..000000000
--- a/Directions Example/Info.plist
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- en
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- 2.4.0
- CFBundleSignature
- ????
- CFBundleVersion
- 90
- LSRequiresIPhoneOS
-
- NSLocationWhenInUseUsageDescription
- Use the user location to fetch a route from their current position.
- UILaunchStoryboardName
- Launch Screen
- UIRequiredDeviceCapabilities
-
- armv7
-
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
-
-
diff --git a/Directions Example/Launch Screen.storyboard b/Directions Example/Launch Screen.storyboard
deleted file mode 100644
index 026a2622b..000000000
--- a/Directions Example/Launch Screen.storyboard
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/DirectionsPlayground/DirectionsPlayground.xcodeproj/project.pbxproj b/DirectionsPlayground/DirectionsPlayground.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..21686b8cf
--- /dev/null
+++ b/DirectionsPlayground/DirectionsPlayground.xcodeproj/project.pbxproj
@@ -0,0 +1,433 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 55;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ E2E47CC227F62E06003C859F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E2E47CC127F62E06003C859F /* Assets.xcassets */; };
+ E2E47CC527F62E06003C859F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E2E47CC427F62E06003C859F /* Preview Assets.xcassets */; };
+ E2E47CE127F62E8F003C859F /* QueriesList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E47CDA27F62E8F003C859F /* QueriesList.swift */; };
+ E2E47CE227F62E8F003C859F /* DirectionsPlayground.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E47CDB27F62E8F003C859F /* DirectionsPlayground.swift */; };
+ E2E47CE327F62E8F003C859F /* RoutesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E47CDC27F62E8F003C859F /* RoutesView.swift */; };
+ E2E47CE427F62E8F003C859F /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E47CDD27F62E8F003C859F /* Query.swift */; };
+ E2E47CE527F62E8F003C859F /* QueryEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E47CDE27F62E8F003C859F /* QueryEditor.swift */; };
+ E2E47CE627F62E8F003C859F /* WaypointsEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E47CDF27F62E8F003C859F /* WaypointsEditor.swift */; };
+ E2E47CE727F62E8F003C859F /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E47CE027F62E8F003C859F /* Storage.swift */; };
+ E2E47CEB27F62EB7003C859F /* MapboxDirections in Frameworks */ = {isa = PBXBuildFile; productRef = E2E47CEA27F62EB7003C859F /* MapboxDirections */; };
+ E2E47CEF27F6EF7D003C859F /* InfoButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E47CEE27F6EF7D003C859F /* InfoButton.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ E2E47CBA27F62E05003C859F /* DirectionsPlayground.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DirectionsPlayground.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ E2E47CC127F62E06003C859F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ E2E47CC427F62E06003C859F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
+ E2E47CDA27F62E8F003C859F /* QueriesList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueriesList.swift; sourceTree = ""; };
+ E2E47CDB27F62E8F003C859F /* DirectionsPlayground.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectionsPlayground.swift; sourceTree = ""; };
+ E2E47CDC27F62E8F003C859F /* RoutesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutesView.swift; sourceTree = ""; };
+ E2E47CDD27F62E8F003C859F /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = ""; };
+ E2E47CDE27F62E8F003C859F /* QueryEditor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryEditor.swift; sourceTree = ""; };
+ E2E47CDF27F62E8F003C859F /* WaypointsEditor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WaypointsEditor.swift; sourceTree = ""; };
+ E2E47CE027F62E8F003C859F /* Storage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; };
+ E2E47CE827F62EAE003C859F /* mapbox-directions-swift */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "mapbox-directions-swift"; path = ..; sourceTree = ""; };
+ E2E47CED27F630BE003C859F /* DirectionsPlayground.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DirectionsPlayground.entitlements; sourceTree = ""; };
+ E2E47CEE27F6EF7D003C859F /* InfoButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoButton.swift; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ E2E47CB727F62E05003C859F /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ E2E47CEB27F62EB7003C859F /* MapboxDirections in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ E2E47CB127F62E05003C859F = {
+ isa = PBXGroup;
+ children = (
+ E2E47CE827F62EAE003C859F /* mapbox-directions-swift */,
+ E2E47CBC27F62E05003C859F /* DirectionsPlayground */,
+ E2E47CBB27F62E05003C859F /* Products */,
+ E2E47CE927F62EB7003C859F /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ E2E47CBB27F62E05003C859F /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ E2E47CBA27F62E05003C859F /* DirectionsPlayground.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ E2E47CBC27F62E05003C859F /* DirectionsPlayground */ = {
+ isa = PBXGroup;
+ children = (
+ E2E47CED27F630BE003C859F /* DirectionsPlayground.entitlements */,
+ E2E47CD927F62E78003C859F /* Sources */,
+ E2E47CC127F62E06003C859F /* Assets.xcassets */,
+ E2E47CC327F62E06003C859F /* Preview Content */,
+ );
+ path = DirectionsPlayground;
+ sourceTree = "";
+ };
+ E2E47CC327F62E06003C859F /* Preview Content */ = {
+ isa = PBXGroup;
+ children = (
+ E2E47CC427F62E06003C859F /* Preview Assets.xcassets */,
+ );
+ path = "Preview Content";
+ sourceTree = "";
+ };
+ E2E47CD927F62E78003C859F /* Sources */ = {
+ isa = PBXGroup;
+ children = (
+ E2E47CDB27F62E8F003C859F /* DirectionsPlayground.swift */,
+ E2E47CDA27F62E8F003C859F /* QueriesList.swift */,
+ E2E47CDD27F62E8F003C859F /* Query.swift */,
+ E2E47CDE27F62E8F003C859F /* QueryEditor.swift */,
+ E2E47CDC27F62E8F003C859F /* RoutesView.swift */,
+ E2E47CE027F62E8F003C859F /* Storage.swift */,
+ E2E47CDF27F62E8F003C859F /* WaypointsEditor.swift */,
+ E2E47CEE27F6EF7D003C859F /* InfoButton.swift */,
+ );
+ path = Sources;
+ sourceTree = "";
+ };
+ E2E47CE927F62EB7003C859F /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ E2E47CB927F62E05003C859F /* DirectionsPlayground */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = E2E47CC827F62E06003C859F /* Build configuration list for PBXNativeTarget "DirectionsPlayground" */;
+ buildPhases = (
+ E2E47CB627F62E05003C859F /* Sources */,
+ E2E47CB727F62E05003C859F /* Frameworks */,
+ E2E47CB827F62E05003C859F /* Resources */,
+ E2E47CEC27F62F0D003C859F /* Apply Mapbox Access Token */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = DirectionsPlayground;
+ packageProductDependencies = (
+ E2E47CEA27F62EB7003C859F /* MapboxDirections */,
+ );
+ productName = DirectionsPlayground;
+ productReference = E2E47CBA27F62E05003C859F /* DirectionsPlayground.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ E2E47CB227F62E05003C859F /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1330;
+ LastUpgradeCheck = 1330;
+ TargetAttributes = {
+ E2E47CB927F62E05003C859F = {
+ CreatedOnToolsVersion = 13.3;
+ LastSwiftMigration = 1330;
+ };
+ };
+ };
+ buildConfigurationList = E2E47CB527F62E05003C859F /* Build configuration list for PBXProject "DirectionsPlayground" */;
+ compatibilityVersion = "Xcode 13.0";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = E2E47CB127F62E05003C859F;
+ productRefGroup = E2E47CBB27F62E05003C859F /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ E2E47CB927F62E05003C859F /* DirectionsPlayground */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ E2E47CB827F62E05003C859F /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ E2E47CC527F62E06003C859F /* Preview Assets.xcassets in Resources */,
+ E2E47CC227F62E06003C859F /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ E2E47CEC27F62F0D003C859F /* Apply Mapbox Access Token */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "$(TARGET_BUILD_DIR)/$(INFOPLIST_PATH)",
+ );
+ name = "Apply Mapbox Access Token";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "# Look for a global file named 'mapbox' or '.mapbox' within the home directory\ntoken_file=~/.mapbox\ntoken_file2=~/mapbox\ntoken=\"$(cat $token_file 2>/dev/null || cat $token_file2 2>/dev/null)\"\nif [ \"$token\" ]; then\n plutil -replace MGLMapboxAccessToken -string $token \"$TARGET_BUILD_DIR/$INFOPLIST_PATH\"\nelse\n echo 'warning: Missing Mapbox access token'\n open 'https://account.mapbox.com/access-tokens/'\n echo \"warning: Get an access token from , then create a new file at $token_file or $token_file2 that contains the access token.\"\nfi\n";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ E2E47CB627F62E05003C859F /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ E2E47CE527F62E8F003C859F /* QueryEditor.swift in Sources */,
+ E2E47CE727F62E8F003C859F /* Storage.swift in Sources */,
+ E2E47CE427F62E8F003C859F /* Query.swift in Sources */,
+ E2E47CEF27F6EF7D003C859F /* InfoButton.swift in Sources */,
+ E2E47CE227F62E8F003C859F /* DirectionsPlayground.swift in Sources */,
+ E2E47CE327F62E8F003C859F /* RoutesView.swift in Sources */,
+ E2E47CE127F62E8F003C859F /* QueriesList.swift in Sources */,
+ E2E47CE627F62E8F003C859F /* WaypointsEditor.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ E2E47CC627F62E06003C859F /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.4;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ E2E47CC727F62E06003C859F /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.4;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ E2E47CC927F62E06003C859F /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = DirectionsPlayground/DirectionsPlayground.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"DirectionsPlayground/Preview Content\"";
+ DEVELOPMENT_TEAM = GJZR2MEM28;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = mapbox.DirectionsPlayground;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SUPPORTS_MACCATALYST = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2,6";
+ };
+ name = Debug;
+ };
+ E2E47CCA27F62E06003C859F /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = DirectionsPlayground/DirectionsPlayground.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"DirectionsPlayground/Preview Content\"";
+ DEVELOPMENT_TEAM = GJZR2MEM28;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = mapbox.DirectionsPlayground;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SUPPORTS_MACCATALYST = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2,6";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ E2E47CB527F62E05003C859F /* Build configuration list for PBXProject "DirectionsPlayground" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ E2E47CC627F62E06003C859F /* Debug */,
+ E2E47CC727F62E06003C859F /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ E2E47CC827F62E06003C859F /* Build configuration list for PBXNativeTarget "DirectionsPlayground" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ E2E47CC927F62E06003C859F /* Debug */,
+ E2E47CCA27F62E06003C859F /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ E2E47CEA27F62EB7003C859F /* MapboxDirections */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = MapboxDirections;
+ };
+/* End XCSwiftPackageProductDependency section */
+ };
+ rootObject = E2E47CB227F62E05003C859F /* Project object */;
+}
diff --git a/DirectionsPlayground/DirectionsPlayground.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/DirectionsPlayground/DirectionsPlayground.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..919434a62
--- /dev/null
+++ b/DirectionsPlayground/DirectionsPlayground.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/DirectionsPlayground/DirectionsPlayground.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/DirectionsPlayground/DirectionsPlayground.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 000000000..18d981003
--- /dev/null
+++ b/DirectionsPlayground/DirectionsPlayground.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/DirectionsPlayground/DirectionsPlayground.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DirectionsPlayground/DirectionsPlayground.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 000000000..17ece073d
--- /dev/null
+++ b/DirectionsPlayground/DirectionsPlayground.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,43 @@
+{
+ "object": {
+ "pins": [
+ {
+ "package": "OHHTTPStubs",
+ "repositoryURL": "https://github.com/AliSoftware/OHHTTPStubs",
+ "state": {
+ "branch": null,
+ "revision": "12f19662426d0434d6c330c6974d53e2eb10ecd9",
+ "version": "9.1.0"
+ }
+ },
+ {
+ "package": "Polyline",
+ "repositoryURL": "https://github.com/raphaelmor/Polyline.git",
+ "state": {
+ "branch": null,
+ "revision": "554a15b15ff33cf6757f4cb693c5c285fe58694e",
+ "version": "5.0.3"
+ }
+ },
+ {
+ "package": "swift-argument-parser",
+ "repositoryURL": "https://github.com/apple/swift-argument-parser",
+ "state": {
+ "branch": null,
+ "revision": "82905286cc3f0fa8adc4674bf49437cab65a8373",
+ "version": "1.1.1"
+ }
+ },
+ {
+ "package": "Turf",
+ "repositoryURL": "https://github.com/mapbox/turf-swift.git",
+ "state": {
+ "branch": null,
+ "revision": "a7db78a2281ffe79966443ebe5687766d6c024c7",
+ "version": "2.3.0"
+ }
+ }
+ ]
+ },
+ "version": 1
+}
diff --git a/DirectionsPlayground/DirectionsPlayground/Assets.xcassets/AccentColor.colorset/Contents.json b/DirectionsPlayground/DirectionsPlayground/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 000000000..eb8789700
--- /dev/null
+++ b/DirectionsPlayground/DirectionsPlayground/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/DirectionsPlayground/DirectionsPlayground/Assets.xcassets/AppIcon.appiconset/Contents.json b/DirectionsPlayground/DirectionsPlayground/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..9221b9bb1
--- /dev/null
+++ b/DirectionsPlayground/DirectionsPlayground/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/DirectionsPlayground/DirectionsPlayground/Assets.xcassets/Contents.json b/DirectionsPlayground/DirectionsPlayground/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..73c00596a
--- /dev/null
+++ b/DirectionsPlayground/DirectionsPlayground/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/DirectionsPlayground/DirectionsPlayground/DirectionsPlayground.entitlements b/DirectionsPlayground/DirectionsPlayground/DirectionsPlayground.entitlements
new file mode 100644
index 000000000..ee95ab7e5
--- /dev/null
+++ b/DirectionsPlayground/DirectionsPlayground/DirectionsPlayground.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.network.client
+
+
+
diff --git a/DirectionsPlayground/DirectionsPlayground/Preview Content/Preview Assets.xcassets/Contents.json b/DirectionsPlayground/DirectionsPlayground/Preview Content/Preview Assets.xcassets/Contents.json
new file mode 100644
index 000000000..73c00596a
--- /dev/null
+++ b/DirectionsPlayground/DirectionsPlayground/Preview Content/Preview Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/DirectionsPlayground/DirectionsPlayground/Sources/DirectionsPlayground.swift b/DirectionsPlayground/DirectionsPlayground/Sources/DirectionsPlayground.swift
new file mode 100644
index 000000000..0ae8c8bba
--- /dev/null
+++ b/DirectionsPlayground/DirectionsPlayground/Sources/DirectionsPlayground.swift
@@ -0,0 +1,13 @@
+import SwiftUI
+
+@main
+struct DirectionsPlayground: App {
+ var body: some Scene {
+ WindowGroup {
+ NavigationView {
+ QueriesList()
+ }
+ .navigationViewStyle(.stack)
+ }
+ }
+}
diff --git a/DirectionsPlayground/DirectionsPlayground/Sources/InfoButton.swift b/DirectionsPlayground/DirectionsPlayground/Sources/InfoButton.swift
new file mode 100644
index 000000000..f438b8da8
--- /dev/null
+++ b/DirectionsPlayground/DirectionsPlayground/Sources/InfoButton.swift
@@ -0,0 +1,38 @@
+import SwiftUI
+
+struct InfoButton: View {
+ enum DocUrl {
+ case waypointAllowsSnappingToClosedRoad
+ case waypointAllowsArrivingOnOppositeSide
+ case separatesLegs
+ }
+
+ let docUrl: DocUrl
+ @Environment(\.openURL) var openURL
+
+ var body: some View {
+ Button {
+ openURL(docUrl.url)
+ } label: {
+ Image(systemName: "info.circle")
+ }
+ .buttonStyle(.bordered)
+ }
+}
+
+extension InfoButton.DocUrl {
+ var urlString: String {
+ switch self {
+ case .waypointAllowsArrivingOnOppositeSide:
+ return "https://docs.mapbox.com/ios/directions/api/2.3.0/Classes/Waypoint.html#/s:16MapboxDirections8WaypointC28allowsArrivingOnOppositeSideSbvp"
+ case .waypointAllowsSnappingToClosedRoad:
+ return "https://docs.mapbox.com/ios/directions/api/2.3.0/Classes/Waypoint.html#/s:16MapboxDirections8WaypointC26allowsSnappingToClosedRoadSbvp"
+ case .separatesLegs:
+ return "https://docs.mapbox.com/ios/directions/api/2.3.0/Classes/Waypoint.html#/s:16MapboxDirections8WaypointC13separatesLegsSbvp"
+ }
+ }
+
+ var url: URL {
+ URL(string: urlString)!
+ }
+}
diff --git a/DirectionsPlayground/DirectionsPlayground/Sources/QueriesList.swift b/DirectionsPlayground/DirectionsPlayground/Sources/QueriesList.swift
new file mode 100644
index 000000000..ab8110a22
--- /dev/null
+++ b/DirectionsPlayground/DirectionsPlayground/Sources/QueriesList.swift
@@ -0,0 +1,52 @@
+import Foundation
+import SwiftUI
+
+struct QueriesList: View {
+ @State
+ var queries: [Query] = [] {
+ didSet { saveQueries() }
+ }
+
+ var body: some View {
+ List {
+ ForEach($queries) { $query in
+ NavigationLink(
+ destination: QueryEditor(query: $query),
+ label: {
+ Text(query.name)
+ })
+ }
+ }
+ .onAppear { loadQueries() }
+ .toolbar {
+ ToolbarItem(placement: .primaryAction) {
+ Button { newQuery() }
+ label: { Image(systemName: "plus") }
+ }
+ }
+ .navigationTitle("Saved Queries")
+ }
+
+ private func loadQueries() {
+ do {
+ queries = try Storage.shared.load() ?? []
+ }
+ catch {
+ print(error)
+ }
+ }
+
+ private func newQuery() {
+ let newQuery = Query.make()
+ queries.append(newQuery)
+ }
+
+ private func saveQueries() {
+ do {
+ try Storage.shared.save(queries)
+ }
+ catch {
+ print(error)
+ }
+ }
+}
diff --git a/DirectionsPlayground/DirectionsPlayground/Sources/Query.swift b/DirectionsPlayground/DirectionsPlayground/Sources/Query.swift
new file mode 100644
index 000000000..aff254c43
--- /dev/null
+++ b/DirectionsPlayground/DirectionsPlayground/Sources/Query.swift
@@ -0,0 +1,12 @@
+import Foundation
+
+struct Query: Codable, Identifiable {
+ let id: String
+ var name: String
+ var waypoints: [Waypoint]
+
+ static func make() -> Query {
+ let uuid = UUID().uuidString
+ return .init(id: uuid, name: uuid, waypoints: .defaultWaypoints)
+ }
+}
diff --git a/DirectionsPlayground/DirectionsPlayground/Sources/QueryEditor.swift b/DirectionsPlayground/DirectionsPlayground/Sources/QueryEditor.swift
new file mode 100644
index 000000000..527eaa56f
--- /dev/null
+++ b/DirectionsPlayground/DirectionsPlayground/Sources/QueryEditor.swift
@@ -0,0 +1,67 @@
+import Foundation
+import SwiftUI
+import Combine
+import MapboxDirections
+
+struct QueryEditor: View {
+ @State var routes: [Route] = [] {
+ didSet {
+ showRoutes = !routes.isEmpty
+ }
+ }
+ @State var error: DirectionsError?
+ @State var showRoutes: Bool = false
+ @Binding var query: Query
+
+ var body: some View {
+ VStack {
+ WaypointsEditor(waypoints: $query.waypoints)
+ .toolbar(content: {
+ ToolbarItem(placement: ToolbarItemPlacement.navigationBarTrailing) {
+ NavigationLink(
+ destination: RoutesView(routes: routes),
+ isActive: $showRoutes,
+ label: {
+ Button("Calculate") {
+ loadRoutes(for: query)
+ }.buttonStyle(.borderless)
+ })
+ }
+ })
+ .navigationTitle("Edit Route Waypoints")
+
+ }
+ .alert(isPresented: .constant(error != nil), error: error) {
+ Button("Ok") {
+ error = nil
+ }
+ }
+ }
+
+ func loadRoutes(for query: Query) {
+ let options = RouteOptions(waypoints: query.waypoints.map(\.native))
+ print("Calculating route for \(options.waypoints)")
+ options.includesSteps = true
+ options.routeShapeResolution = .full
+ options.attributeOptions = [.congestionLevel, .maximumSpeedLimit]
+
+ Directions.shared.calculate(options) { (session, result) in
+ switch result {
+ case let .failure(error):
+ self.error = error
+ case let .success(response):
+ self.routes = response.routes ?? []
+ }
+ }
+ }
+}
+
+extension Array where Element == Waypoint {
+ static var defaultWaypoints: Self {
+ [
+ .init(id: .init(), latitude: 38.9131752, longitude: -77.0324047, name: "Mapbox"),
+ .init(id: .init(), latitude: 38.8906572, longitude: -77.0090701, name: "Capitol"),
+ .init(id: .init(), latitude: 38.8977000, longitude: -77.0365000, name: "White House"),
+ ]
+ }
+}
diff --git a/Directions Example/ContentView.swift b/DirectionsPlayground/DirectionsPlayground/Sources/RoutesView.swift
similarity index 56%
rename from Directions Example/ContentView.swift
rename to DirectionsPlayground/DirectionsPlayground/Sources/RoutesView.swift
index d8612c6da..272183bfb 100644
--- a/Directions Example/ContentView.swift
+++ b/DirectionsPlayground/DirectionsPlayground/Sources/RoutesView.swift
@@ -1,79 +1,21 @@
import Foundation
import SwiftUI
-import Combine
import MapboxDirections
-final class DirectionsViewModel: ObservableObject {
- private let distanceFormatter: LengthFormatter = .init()
- private let travelTimeFormatter: DateComponentsFormatter = .init()
+struct RoutesView: View {
+ private static let distanceFormatter: LengthFormatter = .init()
+ private static let travelTimeFormatter: DateComponentsFormatter = {
+ let f = DateComponentsFormatter()
+ f.unitsStyle = .short
+ return f
+ }()
- @Published
- var routes: [Route] = []
-
- init() {
- travelTimeFormatter.unitsStyle = .short
- }
-
- func loadRoutes() {
- let startPoint = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.9131752, longitude: -77.0324047),
- name: "Mapbox")
- let stopPoint = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.89065720, longitude: -77.0090701),
- name: "Capitol")
- let endPoint = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.8977, longitude: -77.0365),
- name: "White House")
- let options = RouteOptions(waypoints: [startPoint, stopPoint, endPoint])
- options.includesSteps = true
- options.routeShapeResolution = .full
- options.attributeOptions = [.congestionLevel, .maximumSpeedLimit]
-
- Directions.shared.calculate(options) { (session, result) in
- switch result {
- case let .failure(error):
- print("Error calculating directions: \(error)")
- case let .success(response):
- self.routes = response.routes ?? []
- }
- }
- }
-
- func formattedDistance(for route: Route) -> String {
- return distanceFormatter.string(fromMeters: route.distance)
- }
-
- func formattedTravelTime(for route: Route) -> String {
- return travelTimeFormatter.string(from: route.expectedTravelTime)!
- }
-
- func formattedTypicalTravelTime(for route: Route) -> String {
- if let typicalTravelTime = route.typicalTravelTime,
- let formattedTypicalTravelTime = travelTimeFormatter.string(from: typicalTravelTime) {
- return formattedTypicalTravelTime
- }
- else {
- return "Not available"
- }
- }
-
- func stepDescriptions(for step: RouteStep) -> String {
- var description: String = ""
- let direction = step.maneuverDirection?.rawValue ?? "none"
- description.append("\(step.instructions) [\(step.maneuverType) \(direction)]")
- if step.distance > 0 {
- let formattedDistance = distanceFormatter.string(fromMeters: step.distance)
- description.append(" (\(step.transportType) for \(formattedDistance))")
- }
- return description
- }
-}
-
-struct ContentView: View {
- @ObservedObject
- var vm: DirectionsViewModel
+ let routes: [Route]
var body: some View {
ScrollView {
LazyVStack(spacing: 10, content: {
- ForEach(vm.routes, id: \.distance) { route in
+ ForEach(routes, id: \.distance) { route in
VStack(alignment: .leading, spacing: 3) {
headerView(for: route)
ForEach(0.. String {
+ return Self.distanceFormatter.string(fromMeters: route.distance)
+ }
+
+ func formattedTravelTime(for route: Route) -> String {
+ return Self.travelTimeFormatter.string(from: route.expectedTravelTime)!
+ }
+
+ func formattedTypicalTravelTime(for route: Route) -> String {
+ if let typicalTravelTime = route.typicalTravelTime,
+ let formattedTypicalTravelTime = Self.travelTimeFormatter.string(from: typicalTravelTime) {
+ return formattedTypicalTravelTime
+ }
+ else {
+ return "Not available"
+ }
+ }
+
+ func stepDescriptions(for step: RouteStep) -> String {
+ var description: String = ""
+ let direction = step.maneuverDirection?.rawValue ?? "none"
+ description.append("\(step.instructions) [\(step.maneuverType) \(direction)]")
+ if step.distance > 0 {
+ let formattedDistance = Self.distanceFormatter.string(fromMeters: step.distance)
+ description.append(" (\(step.transportType) for \(formattedDistance))")
+ }
+ return description
+ }
}
diff --git a/DirectionsPlayground/DirectionsPlayground/Sources/Storage.swift b/DirectionsPlayground/DirectionsPlayground/Sources/Storage.swift
new file mode 100644
index 000000000..061816c33
--- /dev/null
+++ b/DirectionsPlayground/DirectionsPlayground/Sources/Storage.swift
@@ -0,0 +1,22 @@
+import Foundation
+
+final class Storage {
+ static let shared: Storage = .init()
+
+ private enum K {
+ static let saveKey: String = "mapbox-directions-queries"
+ }
+
+ func save(_ queries: [Query]) throws {
+ let coder = JSONEncoder()
+ let data = try coder.encode(queries)
+ UserDefaults.standard.setValue(data, forKey: K.saveKey)
+ }
+
+ func load() throws -> [Query]? {
+ let decoder = JSONDecoder()
+ return try UserDefaults.standard.data(forKey: K.saveKey).map {
+ try decoder.decode([Query].self, from: $0)
+ }
+ }
+}
diff --git a/DirectionsPlayground/DirectionsPlayground/Sources/WaypointsEditor.swift b/DirectionsPlayground/DirectionsPlayground/Sources/WaypointsEditor.swift
new file mode 100644
index 000000000..62c0127fc
--- /dev/null
+++ b/DirectionsPlayground/DirectionsPlayground/Sources/WaypointsEditor.swift
@@ -0,0 +1,119 @@
+import Foundation
+import SwiftUI
+import CoreLocation
+import MapboxDirections
+
+struct Waypoint: Identifiable, Hashable, Codable {
+ let id: UUID
+ var latitude: CLLocationDegrees = 0
+ var longitude: CLLocationDegrees = 0
+ var name: String = ""
+ var allowsSnappingToClosedRoad: Bool = false
+ var allowsArrivingOnOppositeSide: Bool = false
+ var separatesLegs: Bool = true
+
+ var native: MapboxDirections.Waypoint {
+ let waypoint = MapboxDirections.Waypoint(coordinate: .init(latitude: latitude, longitude: longitude), name: name)
+ waypoint.allowsSnappingToClosedRoad = allowsSnappingToClosedRoad
+ waypoint.allowsArrivingOnOppositeSide = allowsArrivingOnOppositeSide
+ waypoint.separatesLegs = separatesLegs
+ return waypoint
+ }
+
+ static func make() -> Waypoint {
+ .init(id: .init())
+ }
+}
+
+struct WaypointsEditor: View {
+ @Binding
+ var waypoints: [Waypoint]
+
+ var body: some View {
+ List {
+ ForEach($waypoints) { $waypoint in
+ HStack(alignment: .top) {
+ WaypointView(waypoint: $waypoint)
+
+ Menu {
+ Button("Insert Above") {
+ addNewWaypoint(before: waypoint)
+ }
+ Button("Insert Below") {
+ addNewWaypoint(after: waypoint)
+ }
+ } label: {
+ Image(systemName: "ellipsis")
+ .frame(width: 30, height: 30, alignment: .center)
+ }.menuStyle(.borderlessButton)
+ }
+ }
+ .onMove { indices, newOffset in
+ waypoints.move(fromOffsets: indices, toOffset: newOffset)
+ }
+ .onDelete { indexSet in
+ waypoints.remove(atOffsets: indexSet)
+ }
+ }
+ .listStyle(InsetGroupedListStyle())
+ .toolbar {
+ ToolbarItem(placement: ToolbarItemPlacement.automatic) {
+ EditButton()
+ }
+ }
+ }
+
+ private func addNewWaypoint(after waypoint: Waypoint) {
+ guard let waypointIndex = waypoints.firstIndex(of: waypoint) else {
+ preconditionFailure("Waypoint is in the array of waypoints")
+ }
+ let insertionIndex = waypoints.index(after: waypointIndex)
+ waypoints.insert(Waypoint.make(), at: insertionIndex)
+ }
+ private func addNewWaypoint(before waypoint: Waypoint) {
+ guard let insertionIndex = waypoints.firstIndex(of: waypoint) else {
+ preconditionFailure("Waypoint is in the array of waypoints")
+ }
+ waypoints.insert(Waypoint.make(), at: insertionIndex)
+ }
+}
+
+struct WaypointView: View {
+ @Binding var waypoint: Waypoint
+ @State private var latitudeString: String
+ @State private var longitudeString: String
+
+ init(waypoint: Binding) {
+ _waypoint = waypoint
+ _latitudeString = State(initialValue: waypoint.wrappedValue.latitude.description)
+ _longitudeString = .init(initialValue: waypoint.wrappedValue.longitude.description)
+ }
+
+ var body: some View {
+ VStack(alignment: .leading) {
+ TextField("Name", text: $waypoint.name)
+ HStack {
+ TextField("Lat", text: $latitudeString, onEditingChanged: { _ in
+ waypoint.latitude = .init(latitudeString) ?? 0
+ })
+ .fixedSize()
+ TextField("Lon", text: $longitudeString, onEditingChanged: { _ in
+ waypoint.longitude = .init(longitudeString) ?? 0
+ })
+ .fixedSize()
+ }
+ HStack {
+ InfoButton(docUrl: .separatesLegs)
+ Toggle("Separates Legs", isOn: $waypoint.separatesLegs)
+ }
+ HStack {
+ InfoButton(docUrl: .waypointAllowsSnappingToClosedRoad)
+ Toggle("Snap To Closed Road", isOn: $waypoint.allowsArrivingOnOppositeSide)
+ }
+ HStack {
+ InfoButton(docUrl: .waypointAllowsArrivingOnOppositeSide)
+ Toggle("Allows Arriving On Opposite Side", isOn: $waypoint.allowsSnappingToClosedRoad)
+ }
+ }
+ }
+}
diff --git a/DirectionsPlayground/README.md b/DirectionsPlayground/README.md
new file mode 100644
index 000000000..44df21cc2
--- /dev/null
+++ b/DirectionsPlayground/README.md
@@ -0,0 +1,8 @@
+# Directions Playground
+
+Shows how to obtain directions using "MapboxDirection" library.
+
+## Requirements
+
+- Xcode 13.3
+- iOS 15
diff --git a/MapboxDirections.xcodeproj/project.pbxproj b/MapboxDirections.xcodeproj/project.pbxproj
index 55add5e3c..e00707b8c 100644
--- a/MapboxDirections.xcodeproj/project.pbxproj
+++ b/MapboxDirections.xcodeproj/project.pbxproj
@@ -332,9 +332,6 @@
DAD31BC324D4ADBF00A1654D /* match-polyline6.json in Resources */ = {isa = PBXBuildFile; fileRef = DAD31BC224D4A8D100A1654D /* match-polyline6.json */; };
DAD31BC424D4ADBF00A1654D /* match-polyline6.json in Resources */ = {isa = PBXBuildFile; fileRef = DAD31BC224D4A8D100A1654D /* match-polyline6.json */; };
DAD31BC524D4ADC000A1654D /* match-polyline6.json in Resources */ = {isa = PBXBuildFile; fileRef = DAD31BC224D4A8D100A1654D /* match-polyline6.json */; };
- DADD27B81E5AAAD800D31FAD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADD27B71E5AAAD800D31FAD /* AppDelegate.swift */; };
- DADD27BF1E5AAAD800D31FAD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DADD27BE1E5AAAD800D31FAD /* Assets.xcassets */; };
- DADD27C81E5AAE3100D31FAD /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DADD27C71E5AAE3100D31FAD /* Launch Screen.storyboard */; };
DAE2DF6823AECB120065057A /* QuickLookTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE2DF6723AECB120065057A /* QuickLookTests.swift */; };
DAE2DF6923AECB120065057A /* QuickLookTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE2DF6723AECB120065057A /* QuickLookTests.swift */; };
DAE2DF6A23AECB120065057A /* QuickLookTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE2DF6723AECB120065057A /* QuickLookTests.swift */; };
@@ -373,7 +370,6 @@
E232F8C52665173C0038CAF3 /* MapboxDirections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA1A10AF1D00F8FF009F82FA /* MapboxDirections.framework */; };
E232F8C62665173C0038CAF3 /* MapboxDirections.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DA1A10AF1D00F8FF009F82FA /* MapboxDirections.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E232F8C9266518480038CAF3 /* Polyline.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = E232F899266513C20038CAF3 /* Polyline.xcframework */; };
- E28E325826662B1E0030807F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28E325726662B1E0030807F /* ContentView.swift */; };
F457FA7A252B9E29007DAEB1 /* Incident.swift in Sources */ = {isa = PBXBuildFile; fileRef = F457FA79252B9E29007DAEB1 /* Incident.swift */; };
F457FA7B252B9E29007DAEB1 /* Incident.swift in Sources */ = {isa = PBXBuildFile; fileRef = F457FA79252B9E29007DAEB1 /* Incident.swift */; };
F457FA7C252B9E29007DAEB1 /* Incident.swift in Sources */ = {isa = PBXBuildFile; fileRef = F457FA79252B9E29007DAEB1 /* Incident.swift */; };
@@ -583,10 +579,6 @@
DAD06E3823A008EB001A917D /* QuickLook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickLook.swift; sourceTree = ""; };
DAD31BC224D4A8D100A1654D /* match-polyline6.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "match-polyline6.json"; sourceTree = ""; };
DADD27B51E5AAAD800D31FAD /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
- DADD27B71E5AAAD800D31FAD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
- DADD27BE1E5AAAD800D31FAD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- DADD27C31E5AAAD800D31FAD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- DADD27C71E5AAE3100D31FAD /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; };
DAE2DF6723AECB120065057A /* QuickLookTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickLookTests.swift; sourceTree = ""; };
DAE2DF6B23AED2280065057A /* RouteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteTests.swift; sourceTree = ""; };
DAE33A1A1F215DF600C06039 /* IntersectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntersectionTests.swift; sourceTree = ""; };
@@ -596,7 +588,6 @@
E232F899266513C20038CAF3 /* Polyline.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Polyline.xcframework; path = Carthage/Build/Polyline.xcframework; sourceTree = ""; };
E232F8AE2665141F0038CAF3 /* Turf.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Turf.xcframework; path = Carthage/Build/Turf.xcframework; sourceTree = ""; };
E232F8BA266516D70038CAF3 /* OHHTTPStubs.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = OHHTTPStubs.xcframework; path = Carthage/Build/OHHTTPStubs.xcframework; sourceTree = ""; };
- E28E325726662B1E0030807F /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
F457FA79252B9E29007DAEB1 /* Incident.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Incident.swift; sourceTree = ""; };
F4CF2C562523B66300A6D0B6 /* TollCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TollCollection.swift; sourceTree = ""; };
F4D785EE1DDD82C100FF4665 /* RouteStepTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteStepTests.swift; sourceTree = ""; };
@@ -920,31 +911,10 @@
path = v5;
sourceTree = "";
};
- DADD27B61E5AAAD800D31FAD /* Directions Example */ = {
- isa = PBXGroup;
- children = (
- DADD27B71E5AAAD800D31FAD /* AppDelegate.swift */,
- E28E325726662B1E0030807F /* ContentView.swift */,
- DADD27CB1E5AAF5600D31FAD /* Supporting Files */,
- );
- path = "Directions Example";
- sourceTree = "";
- };
- DADD27CB1E5AAF5600D31FAD /* Supporting Files */ = {
- isa = PBXGroup;
- children = (
- DADD27BE1E5AAAD800D31FAD /* Assets.xcassets */,
- DADD27C71E5AAE3100D31FAD /* Launch Screen.storyboard */,
- DADD27C31E5AAAD800D31FAD /* Info.plist */,
- );
- name = "Supporting Files";
- sourceTree = "";
- };
DD6254451AE70C1700017857 = {
isa = PBXGroup;
children = (
438BFEBF233D804600457294 /* Documents */,
- DADD27B61E5AAAD800D31FAD /* Directions Example */,
DA6C9D891CAE442B00094FBC /* MapboxDirections */,
2B4382FF2549C1D200A3E38B /* MapboxDirectionsCLI */,
DA6C9D971CAE442B00094FBC /* MapboxDirectionsTests */,
@@ -1361,8 +1331,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- DADD27BF1E5AAAD800D31FAD /* Assets.xcassets in Resources */,
- DADD27C81E5AAE3100D31FAD /* Launch Screen.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1750,8 +1718,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- DADD27B81E5AAAD800D31FAD /* AppDelegate.swift in Sources */,
- E28E325826662B1E0030807F /* ContentView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2232,7 +2198,7 @@
CLANG_ANALYZER_NONNULL = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
- DEVELOPMENT_TEAM = "";
+ DEVELOPMENT_TEAM = GJZR2MEM28;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = "Directions Example/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
@@ -2254,7 +2220,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
- DEVELOPMENT_TEAM = "";
+ DEVELOPMENT_TEAM = GJZR2MEM28;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = "Directions Example/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
diff --git a/README.md b/README.md
index 32ba74b02..8256d92fc 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ Or in your [Swift Package Manager](https://swift.org/package-manager/) Package.s
Then `import MapboxDirections`.
-This repository contains an example application that demonstrates how to use the framework. To run it, you need to use [Carthage](https://github.com/Carthage/Carthage) 0.19 or above to install the dependencies. Detailed documentation is available in the [Mapbox API Documentation](https://docs.mapbox.com/api/navigation/#directions).
+This repository contains an [example application](./DirectionsPlayground/) that demonstrates how to use the framework. Detailed documentation is available in the [Mapbox API Documentation](https://docs.mapbox.com/api/navigation/#directions).
## System requirements
diff --git a/Sources/MapboxDirections/DirectionsError.swift b/Sources/MapboxDirections/DirectionsError.swift
index a4de44e34..909e01da8 100644
--- a/Sources/MapboxDirections/DirectionsError.swift
+++ b/Sources/MapboxDirections/DirectionsError.swift
@@ -110,7 +110,12 @@ public enum DirectionsError: LocalizedError {
*/
case unknown(response: URLResponse?, underlying: Error?, code: String?, message: String?)
-
+
+ public var errorDescription: String? {
+ guard let failureReason = failureReason else { return nil }
+ return failureReason.appending(recoverySuggestion ?? "")
+ }
+
public var failureReason: String? {
switch self {
case .network(_):