From 288e215a249618e74135ba6d9cdc54d5d54898f5 Mon Sep 17 00:00:00 2001 From: Patrick Kladek Date: Sat, 27 Jul 2024 17:55:44 +0200 Subject: [PATCH] update to original MapboxDirections Package --- .../xcshareddata/swiftpm/Package.resolved | 21 ++- MapboxCoreNavigation/CLLocation.swift | 38 ++-- MapboxCoreNavigation/CoreFeedbackEvent.swift | 2 +- MapboxCoreNavigation/DistanceFormatter.swift | 9 +- .../NavigationRouteOptions.swift | 36 ++-- MapboxCoreNavigation/Route.swift | 11 +- MapboxCoreNavigation/RouteController.swift | 117 +++++------- .../RouteControllerDelegate.swift | 78 +++++--- MapboxCoreNavigation/RouteOptions.swift | 12 +- MapboxCoreNavigation/RouteProgress.swift | 128 ++++++------- .../SimulatedLocationManager.swift | 44 ++--- MapboxNavigation/CarPlayManager.swift | 143 ++++++++------- .../CarPlayNavigationViewController.swift | 41 ++++- MapboxNavigation/InstructionLabel.swift | 13 +- MapboxNavigation/InstructionPresenter.swift | 130 ++++++++----- MapboxNavigation/InstructionsBannerView.swift | 3 +- MapboxNavigation/LaneView.swift | 7 +- MapboxNavigation/LanesView.swift | 14 +- MapboxNavigation/NavigationMapView.swift | 173 ++++++++---------- .../NavigationViewController.swift | 129 ++++++------- MapboxNavigation/RouteMapViewController.swift | 4 +- Package.swift | 4 +- 22 files changed, 616 insertions(+), 541 deletions(-) diff --git a/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5635fc2c..e2647fb8 100644 --- a/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -3,10 +3,10 @@ { "identity" : "mapbox-directions-swift", "kind" : "remoteSourceControl", - "location" : "https://github.com/flitsmeister/mapbox-directions-swift", + "location" : "https://github.com/mapbox/mapbox-directions-swift", "state" : { - "revision" : "6c19ecc4e1324887ae3250802b8d13d8d8b3ff2d", - "version" : "0.23.3" + "revision" : "6512320ee7f0091a4c55abe2bc6414f078da88c8", + "version" : "2.14.0" } }, { @@ -36,6 +36,15 @@ "version" : "3.0.1" } }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "41982a3656a71c768319979febd796c6fd111d5c", + "version" : "1.5.0" + } + }, { "identity" : "swiftformat", "kind" : "remoteSourceControl", @@ -48,10 +57,10 @@ { "identity" : "turf-swift", "kind" : "remoteSourceControl", - "location" : "https://github.com/flitsmeister/turf-swift", + "location" : "https://github.com/mapbox/turf-swift.git", "state" : { - "revision" : "b05b4658d1b48eac4127a0d9ebbb5a6f965a8251", - "version" : "0.2.2" + "revision" : "213050191cfcb3d5aa76e1fa90c6ff1e182a42ca", + "version" : "2.8.0" } } ], diff --git a/MapboxCoreNavigation/CLLocation.swift b/MapboxCoreNavigation/CLLocation.swift index 50ba693f..5781ad7b 100644 --- a/MapboxCoreNavigation/CLLocation.swift +++ b/MapboxCoreNavigation/CLLocation.swift @@ -1,5 +1,6 @@ import CoreLocation import MapboxDirections +import Polyline import Turf extension CLLocation { @@ -61,31 +62,38 @@ extension CLLocation { Returns a Boolean value indicating whether the receiver is within a given distance of a route step. */ func isWithin(_ maximumDistance: CLLocationDistance, of routeStep: RouteStep) -> Bool { - guard let closestCoordinate = Polyline(routeStep.coordinates!).closestCoordinate(to: coordinate) else { + guard let closestCoordinate = routeStep.shape?.closestCoordinate(to: self.coordinate) else { return false } - return closestCoordinate.distance < maximumDistance + + return closestCoordinate.coordinate.distance(to: self.coordinate) < maximumDistance } - + // MARK: - Route Snapping func snapped(to legProgress: RouteLegProgress) -> CLLocation? { let coords = self.coordinates(for: legProgress) - - guard let closest = Polyline(coords).closestCoordinate(to: coordinate) else { return nil } - guard let calculatedCourseForLocationOnStep = interpolatedCourse(along: coords) else { return nil } + + guard let closest = LineString(coords).closestCoordinate(to: coordinate) else { return nil } + guard let calculatedCourseForLocationOnStep = self.interpolatedCourse(along: coords) else { return nil } let userCourse = calculatedCourseForLocationOnStep let userCoordinate = closest.coordinate - guard let firstCoordinate = legProgress.leg.steps.first?.coordinates?.first else { return nil } + guard let firstCoordinate = legProgress.leg.steps.first?.shape?.coordinates.first else { return nil } guard self.shouldSnapCourse(toRouteWith: calculatedCourseForLocationOnStep, distanceToFirstCoordinateOnLeg: coordinate.distance(to: firstCoordinate)) else { return nil } - guard closest.distance <= (RouteControllerUserLocationSnappingDistance + horizontalAccuracy) else { + guard closest.distance <= (RouteControllerUserLocationSnappingDistance + self.horizontalAccuracy) else { return nil } - return CLLocation(coordinate: userCoordinate, altitude: altitude, horizontalAccuracy: horizontalAccuracy, verticalAccuracy: verticalAccuracy, course: userCourse, speed: speed, timestamp: timestamp) + return CLLocation(coordinate: userCoordinate, + altitude: self.altitude, + horizontalAccuracy: self.horizontalAccuracy, + verticalAccuracy: self.verticalAccuracy, + course: userCourse, + speed: self.speed, + timestamp: self.timestamp) } /** @@ -93,7 +101,7 @@ extension CLLocation { */ func coordinates(for legProgress: RouteLegProgress) -> [CLLocationCoordinate2D] { let nearbyCoordinates = legProgress.nearbyCoordinates - let stepCoordinates = legProgress.currentStep.coordinates! + let stepCoordinates = legProgress.currentStep.shape!.coordinates // If the upcoming maneuver a sharp turn, only look at the current step for snapping. // Otherwise, we may get false positives from nearby step coordinates @@ -110,7 +118,7 @@ extension CLLocation { } } - if speed <= RouteControllerMaximumSpeedForUsingCurrentStep { + if self.speed <= RouteControllerMaximumSpeedForUsingCurrentStep { return stepCoordinates } @@ -121,17 +129,17 @@ extension CLLocation { Given a location and a series of coordinates, compute what the course should be for a the location. */ func interpolatedCourse(along coordinates: [CLLocationCoordinate2D]) -> CLLocationDirection? { - let nearByPolyline = Polyline(coordinates) + let nearByPolyline = LineString(coordinates) guard let closest = nearByPolyline.closestCoordinate(to: coordinate) else { return nil } - let slicedLineBehind = Polyline(coordinates.reversed()).sliced(from: closest.coordinate, to: coordinates.reversed().last) + let slicedLineBehind = LineString(coordinates.reversed()).sliced(from: closest.coordinate, to: coordinates.reversed().last) let slicedLineInFront = nearByPolyline.sliced(from: closest.coordinate, to: coordinates.last) let userDistanceBuffer: CLLocationDistance = max(speed * RouteControllerDeadReckoningTimeInterval / 2, RouteControllerUserLocationSnappingDistance / 2) - guard let pointBehind = slicedLineBehind.coordinateFromStart(distance: userDistanceBuffer) else { return nil } + guard let pointBehind = slicedLineBehind?.coordinateFromStart(distance: userDistanceBuffer) else { return nil } guard let pointBehindClosest = nearByPolyline.closestCoordinate(to: pointBehind) else { return nil } - guard let pointAhead = slicedLineInFront.coordinateFromStart(distance: userDistanceBuffer) else { return nil } + guard let pointAhead = slicedLineInFront?.coordinateFromStart(distance: userDistanceBuffer) else { return nil } guard let pointAheadClosest = nearByPolyline.closestCoordinate(to: pointAhead) else { return nil } // Get direction of these points diff --git a/MapboxCoreNavigation/CoreFeedbackEvent.swift b/MapboxCoreNavigation/CoreFeedbackEvent.swift index 5935b87c..32b4e9b8 100644 --- a/MapboxCoreNavigation/CoreFeedbackEvent.swift +++ b/MapboxCoreNavigation/CoreFeedbackEvent.swift @@ -33,7 +33,7 @@ class FeedbackEvent: CoreFeedbackEvent { class RerouteEvent: CoreFeedbackEvent { func update(newRoute: Route) { - if let geometry = newRoute.coordinates { + if let geometry = newRoute.shape?.coordinates { eventDictionary["newGeometry"] = Polyline(coordinates: geometry).encodedPolyline eventDictionary["newDistanceRemaining"] = round(newRoute.distance) eventDictionary["newDurationRemaining"] = round(newRoute.expectedTravelTime) diff --git a/MapboxCoreNavigation/DistanceFormatter.swift b/MapboxCoreNavigation/DistanceFormatter.swift index 4ee93564..3e188782 100644 --- a/MapboxCoreNavigation/DistanceFormatter.swift +++ b/MapboxCoreNavigation/DistanceFormatter.swift @@ -122,7 +122,6 @@ public extension NSAttributedString.Key { } /// Provides appropriately formatted, localized descriptions of linear distances. -@objc(MBDistanceFormatter) open class DistanceFormatter: LengthFormatter { /// True to favor brevity over precision. var approx: Bool @@ -164,7 +163,7 @@ open class DistanceFormatter: LengthFormatter { - parameter approximate: approximates the distances. */ - @objc public init(approximate: Bool = false) { + public init(approximate: Bool = false) { self.approx = approximate super.init() self.locale = .nationalizedCurrent @@ -199,7 +198,7 @@ open class DistanceFormatter: LengthFormatter { The user’s `Locale` is used here to set the units. */ - @objc public func string(from distance: CLLocationDistance) -> String { + public func string(from distance: CLLocationDistance) -> String { numberFormatter.positivePrefix = "" numberFormatter.positiveSuffix = "" numberFormatter.decimalSeparator = self.nonFractionalLengthFormatter.numberFormatter.decimalSeparator @@ -208,7 +207,7 @@ open class DistanceFormatter: LengthFormatter { return self.formattedDistance(distance) } - @objc override open func string(fromMeters numberInMeters: Double) -> String { + override open func string(fromMeters numberInMeters: Double) -> String { self.string(from: numberInMeters) } @@ -235,7 +234,7 @@ open class DistanceFormatter: LengthFormatter { `NSAttributedStringKey.quantity` is applied to the numeric quantity. */ - @objc override open func attributedString(for obj: Any, withDefaultAttributes attrs: [NSAttributedString.Key: Any]? = nil) -> NSAttributedString? { + override open func attributedString(for obj: Any, withDefaultAttributes attrs: [NSAttributedString.Key: Any]? = nil) -> NSAttributedString? { guard let distance = obj as? CLLocationDistance else { return nil } diff --git a/MapboxCoreNavigation/NavigationRouteOptions.swift b/MapboxCoreNavigation/NavigationRouteOptions.swift index 370eb412..9156eee4 100644 --- a/MapboxCoreNavigation/NavigationRouteOptions.swift +++ b/MapboxCoreNavigation/NavigationRouteOptions.swift @@ -1,13 +1,12 @@ +import CoreLocation import Foundation import MapboxDirections -import MapboxDirectionsObjc /** A `NavigationRouteOptions` object specifies turn-by-turn-optimized criteria for results returned by the Mapbox Directions API. `NavigationRouteOptions` is a subclass of `RouteOptions` that has been optimized for navigation. Pass an instance of this class into the `Directions.calculate(_:completionHandler:)` method. */ -@objc(MBNavigationRouteOptions) open class NavigationRouteOptions: RouteOptions { /** Initializes a navigation route options object for routes between the given waypoints and an optional profile identifier optimized for navigation. @@ -15,7 +14,7 @@ open class NavigationRouteOptions: RouteOptions { - SeeAlso: [RouteOptions](https://www.mapbox.com/mapbox-navigation-ios/directions/0.10.1/Classes/RouteOptions.html) */ - @objc public required init(waypoints: [Waypoint], profileIdentifier: MBDirectionsProfileIdentifier? = .automobileAvoidingTraffic) { + public required init(waypoints: [Waypoint], profileIdentifier: ProfileIdentifier? = .automobileAvoidingTraffic) { super.init(waypoints: waypoints.map { $0.coordinateAccuracy = -1 return $0 @@ -37,7 +36,7 @@ open class NavigationRouteOptions: RouteOptions { - SeeAlso: [RouteOptions](https://www.mapbox.com/mapbox-navigation-ios/directions/0.19.0/Classes/RouteOptions.html) */ - @objc public convenience init(locations: [CLLocation], profileIdentifier: MBDirectionsProfileIdentifier? = .automobileAvoidingTraffic) { + public convenience init(locations: [CLLocation], profileIdentifier: ProfileIdentifier? = .automobileAvoidingTraffic) { self.init(waypoints: locations.map { Waypoint(location: $0) }, profileIdentifier: profileIdentifier) } @@ -47,12 +46,16 @@ open class NavigationRouteOptions: RouteOptions { - SeeAlso: [RouteOptions](https://www.mapbox.com/mapbox-navigation-ios/directions/0.19.0/Classes/RouteOptions.html) */ - @objc public convenience init(coordinates: [CLLocationCoordinate2D], profileIdentifier: MBDirectionsProfileIdentifier? = .automobileAvoidingTraffic) { + public convenience init(coordinates: [CLLocationCoordinate2D], profileIdentifier: ProfileIdentifier? = .automobileAvoidingTraffic) { self.init(waypoints: coordinates.map { Waypoint(coordinate: $0) }, profileIdentifier: profileIdentifier) } - - @objc public required init?(coder decoder: NSCoder) { - super.init(coder: decoder) + + public required init(from decoder: any Decoder) throws { + try super.init(from: decoder) + } + + public required init(waypoints: [Waypoint], profileIdentifier: ProfileIdentifier? = nil, queryItems: [URLQueryItem]? = nil) { + super.init(waypoints: waypoints, profileIdentifier: profileIdentifier, queryItems: queryItems) } } @@ -63,7 +66,6 @@ open class NavigationRouteOptions: RouteOptions { Note: it is very important you specify the `waypoints` for the route. Usually the only two values for this `IndexSet` will be 0 and the length of the coordinates. Otherwise, all coordinates passed through will be considered waypoints. */ -@objc(MBNavigationMatchOptions) open class NavigationMatchOptions: MatchOptions { /** Initializes a navigation route options object for routes between the given waypoints and an optional profile identifier optimized for navigation. @@ -71,7 +73,7 @@ open class NavigationMatchOptions: MatchOptions { - SeeAlso: [MatchOptions](https://www.mapbox.com/mapbox-navigation-ios/directions/0.19.0/Classes/MatchOptions.html) */ - @objc public required init(waypoints: [Waypoint], profileIdentifier: MBDirectionsProfileIdentifier? = .automobileAvoidingTraffic) { + public required init(waypoints: [Waypoint], profileIdentifier: ProfileIdentifier? = .automobileAvoidingTraffic) { super.init(waypoints: waypoints.map { $0.coordinateAccuracy = -1 return $0 @@ -91,7 +93,7 @@ open class NavigationMatchOptions: MatchOptions { - SeeAlso: [MatchOptions](https://www.mapbox.com/mapbox-navigation-ios/directions/0.19.0/Classes/MatchOptions.html) */ - @objc public convenience init(locations: [CLLocation], profileIdentifier: MBDirectionsProfileIdentifier? = .automobileAvoidingTraffic) { + public convenience init(locations: [CLLocation], profileIdentifier: ProfileIdentifier? = .automobileAvoidingTraffic) { self.init(waypoints: locations.map { Waypoint(location: $0) }, profileIdentifier: profileIdentifier) } @@ -101,11 +103,15 @@ open class NavigationMatchOptions: MatchOptions { - SeeAlso: [MatchOptions](https://www.mapbox.com/mapbox-navigation-ios/directions/0.19.0/Classes/MatchOptions.html) */ - @objc public convenience init(coordinates: [CLLocationCoordinate2D], profileIdentifier: MBDirectionsProfileIdentifier? = .automobileAvoidingTraffic) { + public convenience init(coordinates: [CLLocationCoordinate2D], profileIdentifier: ProfileIdentifier? = .automobileAvoidingTraffic) { self.init(waypoints: coordinates.map { Waypoint(coordinate: $0) }, profileIdentifier: profileIdentifier) } - - @objc public required init?(coder decoder: NSCoder) { - super.init(coder: decoder) + + public required init(waypoints: [Waypoint], profileIdentifier: ProfileIdentifier? = nil, queryItems: [URLQueryItem]? = nil) { + super.init(waypoints: waypoints, profileIdentifier: profileIdentifier, queryItems: queryItems) + } + + public required init(from decoder: any Decoder) throws { + try super.init(from: decoder) } } diff --git a/MapboxCoreNavigation/Route.swift b/MapboxCoreNavigation/Route.swift index 005ef6b4..73387f8e 100644 --- a/MapboxCoreNavigation/Route.swift +++ b/MapboxCoreNavigation/Route.swift @@ -10,12 +10,19 @@ import CoreLocation import MapboxDirections extension Route { - convenience init(jsonFileName: String, waypoints: [CLLocationCoordinate2D], polylineShapeFormat: RouteShapeFormat = .polyline6, bundle: Bundle = .main, accessToken: String) { + static func from(jsonFileName: String, waypoints: [CLLocationCoordinate2D], polylineShapeFormat: RouteShapeFormat = .polyline6, bundle: Bundle = .main, accessToken: String) throws -> Route { let convertedWaypoints = waypoints.compactMap { waypoint in Waypoint(coordinate: waypoint) } let routeOptions = NavigationRouteOptions(waypoints: convertedWaypoints) routeOptions.shapeFormat = polylineShapeFormat - self.init(json: Fixture.JSONFromFileNamed(name: jsonFileName, bundle: bundle), waypoints: convertedWaypoints, options: routeOptions) + + let path = bundle.url(forResource: jsonFileName, withExtension: "json") ?? bundle.url(forResource: jsonFileName, withExtension: "geojson")! + let data = try Data(contentsOf: path) + + let decoder = JSONDecoder() + let result = try decoder.decode(RouteResponse.self, from: data) + + return result.routes!.first! } } diff --git a/MapboxCoreNavigation/RouteController.swift b/MapboxCoreNavigation/RouteController.swift index 92c3b332..0ca97291 100644 --- a/MapboxCoreNavigation/RouteController.swift +++ b/MapboxCoreNavigation/RouteController.swift @@ -11,7 +11,6 @@ import UIKit `RouteController` is responsible for the core navigation logic whereas `NavigationViewController` is responsible for displaying a default drop-in navigation UI. */ -@objc(MBRouteController) open class RouteController: NSObject, Router { /** The number of seconds between attempts to automatically calculate a more optimal route while traveling. @@ -29,12 +28,12 @@ open class RouteController: NSObject, Router { /** The route controller’s delegate. */ - @objc public weak var delegate: RouteControllerDelegate? + public weak var delegate: RouteControllerDelegate? /** The route controller’s associated location manager. */ - @objc public var locationManager: NavigationLocationManager! { + public var locationManager: NavigationLocationManager! { didSet { oldValue.delegate = nil self.locationManager.delegate = self @@ -44,17 +43,17 @@ open class RouteController: NSObject, Router { /** The Directions object used to create the route. */ - @objc public var directions: Directions + public var directions: Directions /** If true, location updates will be simulated when driving through tunnels or other areas where there is none or bad GPS reception. */ - @objc public var isDeadReckoningEnabled = false + public var isDeadReckoningEnabled = false /** If true, the `RouteController` attempts to calculate a more optimal route for the user on an interval defined by `routeControllerProactiveReroutingInterval`. */ - @objc public var reroutesProactively = false + public var reroutesProactively = false /** A `TunnelIntersectionManager` used for animating the use user puck when and if a user enters a tunnel. @@ -73,7 +72,7 @@ open class RouteController: NSObject, Router { /** Details about the user’s progress along the current route, leg, and step. */ - @objc public var routeProgress: RouteProgress { + public var routeProgress: RouteProgress { didSet { var userInfo = [RouteControllerNotificationUserInfoKey: Any]() if let location = locationManager.location { @@ -105,7 +104,7 @@ open class RouteController: NSObject, Router { var userSnapToStepDistanceFromManeuver: CLLocationDistance? /// Describes a reason for rerouting and applying a new route - @objc public enum RerouteReason: Int, CustomStringConvertible { + public enum RerouteReason: Int, CustomStringConvertible { /// When we check for a faster route we can also reroute the user when we just want to update the ETA. For example when the user is driving on a route where a Trafficjam appears, it should update the ETA case ETAUpdate @@ -135,12 +134,11 @@ open class RouteController: NSObject, Router { - parameter directions: The Directions object that created `route`. - parameter locationManager: The associated location manager. */ - @objc(initWithRoute:directions:locationManager:) public init(along route: Route, directions: Directions = Directions.shared, locationManager: NavigationLocationManager = NavigationLocationManager()) { self.directions = directions self.routeProgress = RouteProgress(route: route) self.locationManager = locationManager - self.locationManager.activityType = route.routeOptions.activityType + self.locationManager.activityType = .automotiveNavigation UIDevice.current.isBatteryMonitoringEnabled = true super.init() @@ -157,7 +155,7 @@ open class RouteController: NSObject, Router { deinit { self.endNavigation() - guard let shouldDisable = delegate?.routeControllerShouldDisableBatteryMonitoring?(self) else { + guard let shouldDisable = self.delegate?.routeControllerShouldDisableBatteryMonitoring(self) else { UIDevice.current.isBatteryMonitoringEnabled = false return } @@ -175,7 +173,8 @@ open class RouteController: NSObject, Router { NotificationCenter.default.removeObserver(self) } - @objc private func applicationWillTerminate(_ notification: NSNotification) { + @objc + private func applicationWillTerminate(_ notification: NSNotification) { self.endNavigation() } @@ -184,7 +183,7 @@ open class RouteController: NSObject, Router { Will continue monitoring until `suspendLocationUpdates()` is called. */ - @objc public func resume() { + public func resume() { self.locationManager.delegate = self self.locationManager.startUpdatingLocation() self.locationManager.startUpdatingHeading() @@ -193,7 +192,7 @@ open class RouteController: NSObject, Router { /** Stops monitoring the user’s location along the route. */ - @objc public func suspendLocationUpdates() { + public func suspendLocationUpdates() { self.locationManager.stopUpdatingLocation() self.locationManager.stopUpdatingHeading() self.locationManager.delegate = nil @@ -203,7 +202,7 @@ open class RouteController: NSObject, Router { /** Ends the current navigation session. */ - @objc public func endNavigation() { + public func endNavigation() { self.suspendLocationUpdates() self.suspendNotifications() } @@ -212,7 +211,7 @@ open class RouteController: NSObject, Router { The idealized user location. Snapped to the route line, if applicable, otherwise raw. - seeAlso: snappedLocation, rawLocation */ - @objc public var location: CLLocation? { + public var location: CLLocation? { // If there is no snapped location, and the rawLocation course is unqualified, use the user's heading as long as it is accurate. if self.snappedLocation == nil, let heading, @@ -234,7 +233,7 @@ open class RouteController: NSObject, Router { return nil } - let customSnap = self.delegate?.routeControllerSnap?(rawLocation: raw) + let customSnap = self.delegate?.routeControllerSnap(rawLocation: raw) return customSnap ?? raw.snapped(to: self.routeProgress.currentLegProgress) } @@ -251,17 +250,17 @@ open class RouteController: NSObject, Router { } func updateDistanceToManeuver() { - guard let coordinates = routeProgress.currentLegProgress.currentStep.coordinates, let coordinate = rawLocation?.coordinate else { + guard let coordinates = self.routeProgress.currentLegProgress.currentStep.shape?.coordinates, let coordinate = rawLocation?.coordinate else { self.userSnapToStepDistanceFromManeuver = nil return } - self.userSnapToStepDistanceFromManeuver = Polyline(coordinates).distance(from: coordinate) + self.userSnapToStepDistanceFromManeuver = LineString(coordinates).distance(from: coordinate) } /** If the user is close to an intersection, the tolerance will be lower, otherwise it will be RouteControllerMaximumDistanceBeforeRecalculating */ - @objc public var reroutingTolerance: CLLocationDistance { + public var reroutingTolerance: CLLocationDistance { guard let intersections = routeProgress.currentLegProgress.currentStepProgress.intersectionsIncludingUpcomingManeuverIntersection else { return RouteControllerMaximumDistanceBeforeRecalculating } guard let userLocation = rawLocation else { return RouteControllerMaximumDistanceBeforeRecalculating } @@ -277,7 +276,7 @@ open class RouteController: NSObject, Router { // MARK: - Pre-defined routes for testing - private lazy var testA12ToVeenendaalNormalWithTraffic = Route( + private lazy var testA12ToVeenendaalNormalWithTraffic = try! Route.from( jsonFileName: "A12-To-Veenendaal-Normal-With-Big-Trafficjam", waypoints: [ CLLocationCoordinate2D(latitude: 52.02224357, longitude: 5.78149084), @@ -287,7 +286,7 @@ open class RouteController: NSObject, Router { accessToken: "nonsense" ) - private lazy var testA12ToVeenendaalNormal = Route( + private lazy var testA12ToVeenendaalNormal = try! Route.from( jsonFileName: "A12-To-Veenendaal-Normal", waypoints: [ CLLocationCoordinate2D(latitude: 52.02224357, longitude: 5.78149084), @@ -301,10 +300,11 @@ open class RouteController: NSObject, Router { // MARK: - CLLocationManagerDelegate extension RouteController: CLLocationManagerDelegate { - @objc func interpolateLocation() { + @objc + func interpolateLocation() { guard let location = locationManager.lastKnownLocation else { return } - guard let coordinates = routeProgress.route.coordinates else { return } - let polyline = Polyline(coordinates) + guard let coordinates = routeProgress.route.shape?.coordinates else { return } + let polyline = LineString(coordinates) let distance = location.speed as CLLocationDistance @@ -328,11 +328,11 @@ extension RouteController: CLLocationManagerDelegate { self.locationManager(self.locationManager, didUpdateLocations: [interpolatedLocation]) } - @objc public func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) { + public func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) { self.heading = newHeading } - @objc public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { let filteredLocations = locations.filter(\.isQualified) if !filteredLocations.isEmpty, self.hasFoundOneQualifiedLocation == false { @@ -348,7 +348,7 @@ extension RouteController: CLLocationManagerDelegate { potentialLocation = lastFiltered // `filteredLocations` does not contain good locations and we have found at least one good location previously. } else if self.hasFoundOneQualifiedLocation { - if let lastLocation = locations.last, delegate?.routeController?(self, shouldDiscard: lastLocation) ?? true { + if let lastLocation = locations.last, delegate?.routeController(self, shouldDiscard: lastLocation) ?? true { // Allow the user puck to advance. A stationary puck is not great. self.rawLocation = lastLocation @@ -369,7 +369,7 @@ extension RouteController: CLLocationManagerDelegate { self.rawLocation = location - self.delegate?.routeController?(self, didUpdate: [location]) + self.delegate?.routeController(self, didUpdate: [location]) NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.interpolateLocation), object: nil) @@ -381,9 +381,9 @@ extension RouteController: CLLocationManagerDelegate { self.updateIntersectionIndex(for: currentStepProgress) // Notify observers if the step’s remaining distance has changed. - let polyline = Polyline(routeProgress.currentLegProgress.currentStep.coordinates!) + let polyline = LineString(routeProgress.currentLegProgress.currentStep.shape!.coordinates) if let closestCoordinate = polyline.closestCoordinate(to: location.coordinate) { - let remainingDistance = polyline.distance(from: closestCoordinate.coordinate) + let remainingDistance = polyline.distance(from: closestCoordinate.coordinate)! let distanceTraveled = currentStep.distance - remainingDistance currentStepProgress.distanceTraveled = distanceTraveled NotificationCenter.default.post(name: .routeControllerProgressDidChange, object: self, userInfo: [ @@ -401,7 +401,7 @@ extension RouteController: CLLocationManagerDelegate { self.updateRouteLegProgress(for: location) self.updateVisualInstructionProgress() - guard self.userIsOnRoute(location) || !(self.delegate?.routeController?(self, shouldRerouteFrom: location) ?? true) else { + guard self.userIsOnRoute(location) || !(self.delegate?.routeController(self, shouldRerouteFrom: location) ?? true) else { self.rerouteForDiversion(from: location, along: self.routeProgress) return } @@ -428,7 +428,7 @@ extension RouteController: CLLocationManagerDelegate { } func updateRouteLegProgress(for location: CLLocation) { - let currentDestination = self.routeProgress.currentLeg.destination + guard let currentDestination = self.routeProgress.currentLeg.destination else { return } guard let remainingVoiceInstructions = routeProgress.currentLegProgress.currentStepProgress.remainingSpokenInstructions else { return } if self.routeProgress.currentLegProgress.remainingSteps.count <= 1, remainingVoiceInstructions.count == 0, currentDestination != self.previousArrivalWaypoint { @@ -436,7 +436,7 @@ extension RouteController: CLLocationManagerDelegate { self.routeProgress.currentLegProgress.userHasArrivedAtWaypoint = true - let advancesToNextLeg = self.delegate?.routeController?(self, didArriveAt: currentDestination) ?? true + let advancesToNextLeg = self.delegate?.routeController(self, didArriveAt: currentDestination) ?? true if !self.routeProgress.isFinalLeg, advancesToNextLeg { self.routeProgress.legIndex += 1 @@ -470,9 +470,9 @@ extension RouteController: CLLocationManagerDelegate { If the user is not on the route, they should be rerouted. */ - @objc public func userIsOnRoute(_ location: CLLocation) -> Bool { + public func userIsOnRoute(_ location: CLLocation) -> Bool { // If the user has arrived, do not continue monitor reroutes, step progress, etc - guard !self.routeProgress.currentLegProgress.userHasArrivedAtWaypoint && (self.delegate?.routeController?(self, shouldPreventReroutesWhenArrivingAt: self.routeProgress.currentLeg.destination) ?? true) else { + guard !self.routeProgress.currentLegProgress.userHasArrivedAtWaypoint && (self.delegate?.routeController(self, shouldPreventReroutesWhenArrivingAt: self.routeProgress.currentLeg.destination!) ?? true) else { return true } @@ -579,7 +579,7 @@ extension RouteController: CLLocationManagerDelegate { self.routeProgress = RouteProgress(route: mostSimilarRoute, legIndex: 0, spokenInstructionIndex: self.routeProgress.currentLegProgress.currentStepProgress.spokenInstructionIndex) // Let delegate know - self.delegate?.routeController?(self, didRerouteAlong: mostSimilarRoute, reason: .fasterRoute) + self.delegate?.routeController(self, didRerouteAlong: mostSimilarRoute, reason: .fasterRoute) // Reset flag for notification self.didFindFasterRoute = false @@ -611,7 +611,7 @@ extension RouteController: CLLocationManagerDelegate { self.routeProgress = RouteProgress(route: routeToApply, legIndex: 0, spokenInstructionIndex: self.routeProgress.currentLegProgress.currentStepProgress.spokenInstructionIndex) // Inform delegate - self.delegate?.routeController?(self, didRerouteAlong: routeToApply, reason: .ETAUpdate) + self.delegate?.routeController(self, didRerouteAlong: routeToApply, reason: .ETAUpdate) } } } @@ -630,7 +630,7 @@ extension RouteController: CLLocationManagerDelegate { self.isRerouting = true - self.delegate?.routeController?(self, willRerouteFrom: location) + self.delegate?.routeController(self, willRerouteFrom: location) NotificationCenter.default.post(name: .routeControllerWillReroute, object: self, userInfo: [ RouteControllerNotificationUserInfoKey.locationKey: location ]) @@ -646,7 +646,7 @@ extension RouteController: CLLocationManagerDelegate { strongSelf.isRerouting = false if let error { - strongSelf.delegate?.routeController?(strongSelf, didFailToRerouteWith: error) + strongSelf.delegate?.routeController(strongSelf, didFailToRerouteWith: error) NotificationCenter.default.post(name: .routeControllerDidFailToReroute, object: self, userInfo: [ RouteControllerNotificationUserInfoKey.routingErrorKey: error ]) @@ -657,7 +657,7 @@ extension RouteController: CLLocationManagerDelegate { strongSelf.routeProgress = RouteProgress(route: route, legIndex: 0) strongSelf.routeProgress.currentLegProgress.stepIndex = 0 - strongSelf.delegate?.routeController?(strongSelf, didRerouteAlong: route, reason: .divertedFromRoute) + strongSelf.delegate?.routeController(strongSelf, didRerouteAlong: route, reason: .divertedFromRoute) } } @@ -689,28 +689,7 @@ extension RouteController: CLLocationManagerDelegate { } func getDirections(from location: CLLocation, along progress: RouteProgress, completion: @escaping (_ mostSimilarRoute: Route?, _ routes: [Route]?, _ error: Error?) -> Void) { - if self.delegate?.routeControllerGetDirections?(from: location, along: progress, completion: completion) != true { - // Run the default route calculation if the delegate method is not defined or does not return true - self.routeTask?.cancel() - let options = progress.reroutingOptions(with: location) - - self.lastRerouteLocation = location - - let complete = { (mostSimilarRoute: Route?, routes: [Route]?, error: NSError?) in - completion(mostSimilarRoute, routes, error) - } - - self.routeTask = self.directions.calculate(options) { _, potentialRoutes, potentialError in - - guard let routes = potentialRoutes else { - return complete(nil, nil, potentialError) - } - - // Checks by comparing leg `name` properties and see if the edit distance is within threshold - let mostSimilar = routes.mostSimilar(to: progress.route) - return complete(mostSimilar ?? routes.first, routes, potentialError) - } - } + self.delegate?.routeControllerGetDirections(from: location, along: progress, completion: completion) } func updateDistanceToIntersection(from location: CLLocation) { @@ -725,7 +704,7 @@ extension RouteController: CLLocationManagerDelegate { self.routeProgress.currentLegProgress.currentStepProgress.intersectionsIncludingUpcomingManeuverIntersection = intersections if let upcomingIntersection = routeProgress.currentLegProgress.currentStepProgress.upcomingIntersection { - self.routeProgress.currentLegProgress.currentStepProgress.userDistanceToUpcomingIntersection = Polyline(currentStepProgress.step.coordinates!).distance(from: location.coordinate, to: upcomingIntersection.location) + self.routeProgress.currentLegProgress.currentStepProgress.userDistanceToUpcomingIntersection = LineString(currentStepProgress.step.shape!.coordinates).distance(from: location.coordinate, to: upcomingIntersection.location) } if self.routeProgress.currentLegProgress.currentStepProgress.intersectionDistances == nil { @@ -825,9 +804,9 @@ extension RouteController: CLLocationManagerDelegate { } func updateIntersectionDistances() { - if let coordinates = routeProgress.currentLegProgress.currentStep.coordinates, let intersections = routeProgress.currentLegProgress.currentStep.intersections { - let polyline = Polyline(coordinates) - let distances: [CLLocationDistance] = intersections.map { polyline.distance(from: coordinates.first, to: $0.location) } + if let coordinates = self.routeProgress.currentLegProgress.currentStep.shape?.coordinates, let intersections = routeProgress.currentLegProgress.currentStep.intersections { + let polyline = LineString(coordinates) + let distances: [CLLocationDistance] = intersections.compactMap { polyline.distance(from: coordinates.first, to: $0.location) } self.routeProgress.currentLegProgress.currentStepProgress.intersectionDistances = distances } } @@ -838,8 +817,8 @@ extension RouteController: CLLocationManagerDelegate { /// - route2: Second route to compare /// - Returns: A percentage of the match. Can return `nil` if one of the route's geometry cannot be found. static func matchPercentage(between route: Route, and route2: Route) -> Double? { - guard let currentRouteCoordinates = route.coordinates else { return nil } - guard let otherRouteCoordinates = route2.coordinates else { return nil } + guard let currentRouteCoordinates = route.shape?.coordinates else { return nil } + guard let otherRouteCoordinates = route2.shape?.coordinates else { return nil } // Convert to strings, only taking 4 decimals let currentRouteCoordinatesStrings = currentRouteCoordinates.map { String(format: "%.4f,%.4f", $0.latitude, $0.longitude) } diff --git a/MapboxCoreNavigation/RouteControllerDelegate.swift b/MapboxCoreNavigation/RouteControllerDelegate.swift index 024bb379..07c85b86 100644 --- a/MapboxCoreNavigation/RouteControllerDelegate.swift +++ b/MapboxCoreNavigation/RouteControllerDelegate.swift @@ -5,7 +5,6 @@ import MapboxDirections /** The `RouteControllerDelegate` protocol provides methods for responding to significant events during the user’s traversal of a route monitored by a `RouteController`. */ -@objc(MBRouteControllerDelegate) public protocol RouteControllerDelegate: AnyObject { /** Returns whether the route controller should be allowed to calculate a new route. @@ -16,8 +15,7 @@ public protocol RouteControllerDelegate: AnyObject { - parameter location: The user’s current location. - returns: True to allow the route controller to calculate a new route; false to keep tracking the current route. */ - @objc(routeController:shouldRerouteFromLocation:) - optional func routeController(_ routeController: RouteController, shouldRerouteFrom location: CLLocation) -> Bool + func routeController(_ routeController: RouteController, shouldRerouteFrom location: CLLocation) -> Bool /** Called immediately before the route controller calculates a new route. @@ -27,8 +25,7 @@ public protocol RouteControllerDelegate: AnyObject { - parameter routeController: The route controller that will calculate a new route. - parameter location: The user’s current location. */ - @objc(routeController:willRerouteFromLocation:) - optional func routeController(_ routeController: RouteController, willRerouteFrom location: CLLocation) + func routeController(_ routeController: RouteController, willRerouteFrom location: CLLocation) /** Called when a location has been identified as unqualified to navigate on. @@ -39,8 +36,7 @@ public protocol RouteControllerDelegate: AnyObject { - parameter location: The location that will be discarded. - return: If `true`, the location is discarded and the `RouteController` will not consider it. If `false`, the location will not be thrown out. */ - @objc(routeController:shouldDiscardLocation:) - optional func routeController(_ routeController: RouteController, shouldDiscard location: CLLocation) -> Bool + func routeController(_ routeController: RouteController, shouldDiscard location: CLLocation) -> Bool /** Called immediately after the route controller receives a new route. @@ -51,8 +47,7 @@ public protocol RouteControllerDelegate: AnyObject { - parameter route: The new route. - parameter reason: Describes the reason of the reroute. Could be due to a faster route / updating ETA or when the user diverted from the suggested route */ - @objc(routeController:didRerouteAlongRoute:reason:) - optional func routeController(_ routeController: RouteController, didRerouteAlong route: Route, reason: RouteController.RerouteReason) + func routeController(_ routeController: RouteController, didRerouteAlong route: Route, reason: RouteController.RerouteReason) /** Called when the route controller fails to receive a new route. @@ -62,8 +57,7 @@ public protocol RouteControllerDelegate: AnyObject { - parameter routeController: The route controller that has calculated a new route. - parameter error: An error raised during the process of obtaining a new route. */ - @objc(routeController:didFailToRerouteWithError:) - optional func routeController(_ routeController: RouteController, didFailToRerouteWith error: Error) + func routeController(_ routeController: RouteController, didFailToRerouteWith error: Error) /** Called when the route controller’s location manager receives a location update. @@ -74,8 +68,7 @@ public protocol RouteControllerDelegate: AnyObject { - parameter routeController: The route controller that received the new locations. - parameter locations: The locations that were received from the associated location manager. */ - @objc(routeController:didUpdateLocations:) - optional func routeController(_ routeController: RouteController, didUpdate locations: [CLLocation]) + func routeController(_ routeController: RouteController, didUpdate locations: [CLLocation]) /** Called when the route controller arrives at a waypoint. @@ -87,8 +80,7 @@ public protocol RouteControllerDelegate: AnyObject { - parameter waypoint: The waypoint that the controller has arrived at. - returns: True to advance to the next leg, if any, or false to remain on the completed leg. */ - @objc(routeController:didArriveAtWaypoint:) - optional func routeController(_ routeController: RouteController, didArriveAt waypoint: Waypoint) -> Bool + func routeController(_ routeController: RouteController, didArriveAt waypoint: Waypoint) -> Bool /** Called when the route controller arrives at a waypoint. @@ -99,8 +91,7 @@ public protocol RouteControllerDelegate: AnyObject { - parameter waypoint: The waypoint that the controller has arrived at. - returns: True to prevent the route controller from checking if the user should be rerouted. */ - @objc(routeController:shouldPreventReroutesWhenArrivingAtWaypoint:) - optional func routeController(_ routeController: RouteController, shouldPreventReroutesWhenArrivingAt waypoint: Waypoint) -> Bool + func routeController(_ routeController: RouteController, shouldPreventReroutesWhenArrivingAt waypoint: Waypoint) -> Bool /** Called when the route controller will disable battery monitoring. @@ -110,28 +101,57 @@ public protocol RouteControllerDelegate: AnyObject { - parameter routeController: The route controller that will change the state of battery monitoring. - returns: A bool indicating whether to disable battery monitoring when the RouteController is deinited. */ - @objc(routeControllerShouldDisableBatteryMonitoring:) - optional func routeControllerShouldDisableBatteryMonitoring(_ routeController: RouteController) -> Bool - + func routeControllerShouldDisableBatteryMonitoring(_ routeController: RouteController) -> Bool + /** Allows to customise the calculation of a route. - + If you want to overwrite the default rerouting logic, return true. - + - parameter from: The current location of the user - parameter along: The route progress - parameter completion: Callback function when either the route was successfully calculated or if there was an error - return: True to prevent the route controller from running its own rerouting logic */ - @objc(routeControllerGetDirections:along:completion:) - optional func routeControllerGetDirections(from location: CLLocation, along progress: RouteProgress, completion: @escaping (_ mostSimilarRoute: Route?, _ routes: [Route]?, _ error: Error?) -> Void) -> Bool - + func routeControllerGetDirections(from location: CLLocation, along progress: RouteProgress, completion: @escaping (_ mostSimilarRoute: Route?, _ routes: [Route]?, _ error: Error?) -> Void) + /** Allows to customize the snapping of raw and pre-snapped location. If no implementation is provided, the default snapping will be used. - - parameter rawLocation: The raw location from the controller - - returns: The snapped location or nil if snapping were impossible. + - parameter rawLocation: The raw location from the controller + - returns: The snapped location or nil if snapping were impossible. */ - @objc(routeControllerSnapLocation:) - optional func routeControllerSnap(rawLocation: CLLocation) -> CLLocation? + func routeControllerSnap(rawLocation: CLLocation) -> CLLocation? +} + +extension RouteControllerDelegate { + func routeController(_ routeController: RouteController, shouldRerouteFrom location: CLLocation) -> Bool { + true + } + + func routeController(_ routeController: RouteController, willRerouteFrom location: CLLocation) {} + + func routeController(_ routeController: RouteController, shouldDiscard location: CLLocation) -> Bool { + true + } + + func routeController(_ routeController: RouteController, didRerouteAlong route: Route, reason: RouteController.RerouteReason) {} + + func routeController(_ routeController: RouteController, didFailToRerouteWith error: Error) {} + + func routeController(_ routeController: RouteController, didUpdate locations: [CLLocation]) {} + + func routeController(_ routeController: RouteController, didArriveAt waypoint: Waypoint) -> Bool { + true + } + + func routeController(_ routeController: RouteController, shouldPreventReroutesWhenArrivingAt waypoint: Waypoint) -> Bool { + true + } + + func routeControllerShouldDisableBatteryMonitoring(_ routeController: RouteController) -> Bool { + false + } + + func routeControllerGetDirections(from location: CLLocation, along progress: RouteProgress, completion: @escaping (_ mostSimilarRoute: Route?, _ routes: [Route]?, _ error: Error?) -> Void) {} } diff --git a/MapboxCoreNavigation/RouteOptions.swift b/MapboxCoreNavigation/RouteOptions.swift index 35382ee7..a305567a 100644 --- a/MapboxCoreNavigation/RouteOptions.swift +++ b/MapboxCoreNavigation/RouteOptions.swift @@ -1,11 +1,10 @@ import CoreLocation import MapboxDirections -import MapboxDirectionsObjc extension RouteOptions { var activityType: CLActivityType { switch profileIdentifier { - case MBDirectionsProfileIdentifier.cycling, MBDirectionsProfileIdentifier.walking: + case ProfileIdentifier.cycling, ProfileIdentifier.walking: .fitness default: .automotiveNavigation @@ -20,9 +19,16 @@ extension RouteOptions { */ public func without(waypoint: Waypoint) -> RouteOptions { let waypointsWithoutSpecified = waypoints.filter { $0 != waypoint } - let copy = copy() as! RouteOptions + let copy = try! self.copy() copy.waypoints = waypointsWithoutSpecified return copy } } + +extension Encodable where Self: Decodable { + func copy() throws -> Self { + let data = try JSONEncoder().encode(self) + return try JSONDecoder().decode(Self.self, from: data) + } +} diff --git a/MapboxCoreNavigation/RouteProgress.swift b/MapboxCoreNavigation/RouteProgress.swift index 66305811..32b9b290 100644 --- a/MapboxCoreNavigation/RouteProgress.swift +++ b/MapboxCoreNavigation/RouteProgress.swift @@ -8,19 +8,18 @@ import CarPlay /** `RouteProgress` stores the user’s progress along a route. */ -@objc(MBRouteProgress) open class RouteProgress: NSObject { private static let reroutingAccuracy: CLLocationAccuracy = 90 /** Returns the current `Route`. */ - @objc public let route: Route + public let route: Route /** Index representing current `RouteLeg`. */ - @objc public var legIndex: Int { + public var legIndex: Int { didSet { assert(self.legIndex >= 0 && self.legIndex < self.route.legs.endIndex) // TODO: Set stepIndex to 0 or last index based on whether leg index was incremented or decremented. @@ -31,7 +30,7 @@ open class RouteProgress: NSObject { /** If waypoints are provided in the `Route`, this will contain which leg the user is on. */ - @objc public var currentLeg: RouteLeg { + public var currentLeg: RouteLeg { self.route.legs[self.legIndex] } @@ -46,42 +45,42 @@ open class RouteProgress: NSObject { /** Total distance traveled by user along all legs. */ - @objc public var distanceTraveled: CLLocationDistance { + public var distanceTraveled: CLLocationDistance { self.route.legs.prefix(upTo: self.legIndex).map(\.distance).reduce(0, +) + self.currentLegProgress.distanceTraveled } /** Total seconds remaining on all legs. */ - @objc public var durationRemaining: TimeInterval { + public var durationRemaining: TimeInterval { self.route.legs.suffix(from: self.legIndex + 1).map(\.expectedTravelTime).reduce(0, +) + self.currentLegProgress.durationRemaining } /** Number between 0 and 1 representing how far along the `Route` the user has traveled. */ - @objc public var fractionTraveled: Double { + public var fractionTraveled: Double { self.distanceTraveled / self.route.distance } /** Total distance remaining in meters along route. */ - @objc public var distanceRemaining: CLLocationDistance { + public var distanceRemaining: CLLocationDistance { self.route.distance - self.distanceTraveled } /** Number of waypoints remaining on the current route. */ - @objc public var remainingWaypoints: [Waypoint] { - self.route.legs.suffix(from: self.legIndex).map(\.destination) + public var remainingWaypoints: [Waypoint] { + self.route.legs.suffix(from: self.legIndex).compactMap(\.destination) } /** Returns the progress along the current `RouteLeg`. */ - @objc public var currentLegProgress: RouteLegProgress + public var currentLegProgress: RouteLegProgress /** Tuple containing a `CongestionLevel` and a corresponding `TimeInterval` representing the expected travel time for this segment. @@ -104,7 +103,7 @@ open class RouteProgress: NSObject { - parameter route: The route to follow. - parameter legIndex: Zero-based index indicating the current leg the user is on. */ - @objc public init(route: Route, legIndex: Int = 0, spokenInstructionIndex: Int = 0) { + public init(route: Route, legIndex: Int = 0, spokenInstructionIndex: Int = 0) { self.route = route self.legIndex = legIndex self.currentLegProgress = RouteLegProgress(leg: route.legs[legIndex], stepIndex: 0, spokenInstructionIndex: spokenInstructionIndex) @@ -120,8 +119,8 @@ open class RouteProgress: NSObject { if let segmentCongestionLevels = leg.segmentCongestionLevels, let expectedSegmentTravelTimes = leg.expectedSegmentTravelTimes { for step in leg.steps { - guard let coordinates = step.coordinates else { continue } - let stepCoordinateCount = step.maneuverType == .arrive ? Int(step.coordinateCount) : coordinates.dropLast().count + guard let coordinates = step.shape?.coordinates else { continue } + let stepCoordinateCount = step.maneuverType == .arrive ? coordinates.count : coordinates.dropLast().count let nextManeuverCoordinateIndex = maneuverCoordinateIndex + stepCoordinateCount - 1 guard nextManeuverCoordinateIndex < segmentCongestionLevels.count else { continue } @@ -147,7 +146,9 @@ open class RouteProgress: NSObject { } public var averageCongestionLevelRemainingOnLeg: CongestionLevel? { - let coordinatesLeftOnStepCount = Int(floor(Double(currentLegProgress.currentStepProgress.step.coordinateCount) * self.currentLegProgress.currentStepProgress.fractionTraveled)) + guard let coordinates = self.currentLegProgress.currentStep.shape?.coordinates else { return nil } + + let coordinatesLeftOnStepCount = Int(floor(Double(coordinates.count) * self.currentLegProgress.currentStepProgress.fractionTraveled)) guard coordinatesLeftOnStepCount >= 0 else { return .unknown } @@ -181,21 +182,6 @@ open class RouteProgress: NSObject { } } } - - func reroutingOptions(with current: CLLocation) -> RouteOptions { - let oldOptions = self.route.routeOptions - let user = Waypoint(coordinate: current.coordinate) - - if current.course >= 0 { - user.heading = current.course - user.headingAccuracy = RouteProgress.reroutingAccuracy - } - let newWaypoints = [user] + self.remainingWaypoints - let newOptions = oldOptions.copy() as! RouteOptions - newOptions.waypoints = newWaypoints - - return newOptions - } } /** @@ -206,12 +192,12 @@ open class RouteLegProgress: NSObject { /** Returns the current `RouteLeg`. */ - @objc public let leg: RouteLeg + public let leg: RouteLeg /** Index representing the current step. */ - @objc public var stepIndex: Int { + public var stepIndex: Int { didSet { assert(self.stepIndex >= 0 && self.stepIndex < self.leg.steps.endIndex) self.currentStepProgress = RouteStepProgress(step: self.currentStep) @@ -221,44 +207,44 @@ open class RouteLegProgress: NSObject { /** The remaining steps for user to complete. */ - @objc public var remainingSteps: [RouteStep] { + public var remainingSteps: [RouteStep] { Array(self.leg.steps.suffix(from: self.stepIndex + 1)) } /** Total distance traveled in meters along current leg. */ - @objc public var distanceTraveled: CLLocationDistance { + public var distanceTraveled: CLLocationDistance { self.leg.steps.prefix(upTo: self.stepIndex).map(\.distance).reduce(0, +) + self.currentStepProgress.distanceTraveled } /** Duration remaining in seconds on current leg. */ - @objc public var durationRemaining: TimeInterval { + public var durationRemaining: TimeInterval { self.remainingSteps.map(\.expectedTravelTime).reduce(0, +) + self.currentStepProgress.durationRemaining } /** Distance remaining on the current leg. */ - @objc public var distanceRemaining: CLLocationDistance { + public var distanceRemaining: CLLocationDistance { self.remainingSteps.map(\.distance).reduce(0, +) + self.currentStepProgress.distanceRemaining } /** Number between 0 and 1 representing how far along the current leg the user has traveled. */ - @objc public var fractionTraveled: Double { + public var fractionTraveled: Double { self.distanceTraveled / self.leg.distance } - @objc public var userHasArrivedAtWaypoint = false + public var userHasArrivedAtWaypoint = false /** Returns the `RouteStep` before a given step. Returns `nil` if there is no step prior. */ - @objc public func stepBefore(_ step: RouteStep) -> RouteStep? { + public func stepBefore(_ step: RouteStep) -> RouteStep? { guard let index = leg.steps.firstIndex(of: step) else { return nil } @@ -271,7 +257,7 @@ open class RouteLegProgress: NSObject { /** Returns the `RouteStep` after a given step. Returns `nil` if there is not a step after. */ - @objc public func stepAfter(_ step: RouteStep) -> RouteStep? { + public func stepAfter(_ step: RouteStep) -> RouteStep? { guard let index = leg.steps.firstIndex(of: step) else { return nil } @@ -286,7 +272,7 @@ open class RouteLegProgress: NSObject { If there is no `priorStep`, nil is returned. */ - @objc public var priorStep: RouteStep? { + public var priorStep: RouteStep? { guard self.stepIndex - 1 >= 0 else { return nil } @@ -296,7 +282,7 @@ open class RouteLegProgress: NSObject { /** Returns the current `RouteStep` for the leg the user is on. */ - @objc public var currentStep: RouteStep { + public var currentStep: RouteStep { self.leg.steps[self.stepIndex] } @@ -305,7 +291,7 @@ open class RouteLegProgress: NSObject { If there is no `upcomingStep`, nil is returned. */ - @objc public var upComingStep: RouteStep? { + public var upComingStep: RouteStep? { guard self.stepIndex + 1 < self.leg.steps.endIndex else { return nil } @@ -317,7 +303,7 @@ open class RouteLegProgress: NSObject { If there is no `followOnStep`, nil is returned. */ - @objc public var followOnStep: RouteStep? { + public var followOnStep: RouteStep? { guard self.stepIndex + 2 < self.leg.steps.endIndex else { return nil } @@ -327,14 +313,14 @@ open class RouteLegProgress: NSObject { /** Return bool whether step provided is the current `RouteStep` the user is on. */ - @objc public func isCurrentStep(_ step: RouteStep) -> Bool { + public func isCurrentStep(_ step: RouteStep) -> Bool { step == self.currentStep } /** Returns the progress along the current `RouteStep`. */ - @objc public var currentStepProgress: RouteStepProgress + public var currentStepProgress: RouteStepProgress /** Intializes a new `RouteLegProgress`. @@ -342,7 +328,7 @@ open class RouteLegProgress: NSObject { - parameter leg: Leg on a `Route`. - parameter stepIndex: Current step the user is on. */ - @objc public init(leg: RouteLeg, stepIndex: Int = 0, spokenInstructionIndex: Int = 0) { + public init(leg: RouteLeg, stepIndex: Int = 0, spokenInstructionIndex: Int = 0) { self.leg = leg self.stepIndex = stepIndex self.currentStepProgress = RouteStepProgress(step: leg.steps[stepIndex], spokenInstructionIndex: spokenInstructionIndex) @@ -351,10 +337,10 @@ open class RouteLegProgress: NSObject { /** Returns an array of `CLLocationCoordinate2D` of the prior, current and upcoming step geometry. */ - @objc public var nearbyCoordinates: [CLLocationCoordinate2D] { - let priorCoords = self.priorStep?.coordinates ?? [] - let upcomingCoords = self.upComingStep?.coordinates ?? [] - let currentCoords = self.currentStep.coordinates ?? [] + public var nearbyCoordinates: [CLLocationCoordinate2D] { + let priorCoords = self.priorStep?.shape?.coordinates ?? [] + let upcomingCoords = self.upComingStep?.shape?.coordinates ?? [] + let currentCoords = self.currentStep.shape?.coordinates ?? [] let nearby = priorCoords + currentCoords + upcomingCoords assert(!nearby.isEmpty, "Step must have coordinates") return nearby @@ -367,8 +353,8 @@ open class RouteLegProgress: NSObject { let remainingSteps = self.leg.steps.suffix(from: self.stepIndex) for (currentStepIndex, step) in remainingSteps.enumerated() { - guard let coords = step.coordinates else { continue } - guard let closestCoordOnStep = Polyline(coords).closestCoordinate(to: coordinate) else { continue } + guard let coords = step.shape?.coordinates else { continue } + guard let closestCoordOnStep = LineString(coords).closestCoordinate(to: coordinate) else { continue } let foundIndex = currentStepIndex + self.stepIndex // First time around, currentClosest will be `nil`. @@ -394,29 +380,29 @@ open class RouteStepProgress: NSObject { /** Returns the current `RouteStep`. */ - @objc public let step: RouteStep + public let step: RouteStep /** Returns distance user has traveled along current step. */ - @objc public var distanceTraveled: CLLocationDistance = 0 + public var distanceTraveled: CLLocationDistance = 0 /** Returns distance from user to end of step. */ - @objc public var userDistanceToManeuverLocation: CLLocationDistance = Double.infinity + public var userDistanceToManeuverLocation: CLLocationDistance = Double.infinity /** Total distance in meters remaining on current step. */ - @objc public var distanceRemaining: CLLocationDistance { + public var distanceRemaining: CLLocationDistance { self.step.distance - self.distanceTraveled } /** Number between 0 and 1 representing fraction of current step traveled. */ - @objc public var fractionTraveled: Double { + public var fractionTraveled: Double { guard self.step.distance > 0 else { return 1 } return self.distanceTraveled / self.step.distance } @@ -424,7 +410,7 @@ open class RouteStepProgress: NSObject { /** Number of seconds remaining on current step. */ - @objc public var durationRemaining: TimeInterval { + public var durationRemaining: TimeInterval { (1 - self.fractionTraveled) * self.step.expectedTravelTime } @@ -433,7 +419,7 @@ open class RouteStepProgress: NSObject { - parameter step: Step on a `RouteLeg`. */ - @objc public init(step: RouteStep, spokenInstructionIndex: Int = 0) { + public init(step: RouteStep, spokenInstructionIndex: Int = 0) { self.step = step self.intersectionIndex = 0 self.spokenInstructionIndex = spokenInstructionIndex @@ -444,14 +430,14 @@ open class RouteStepProgress: NSObject { The upcoming `RouteStep` first `Intersection` is added because it is omitted from the current step. */ - @objc public var intersectionsIncludingUpcomingManeuverIntersection: [Intersection]? + public var intersectionsIncludingUpcomingManeuverIntersection: [Intersection]? /** The next intersection the user will travel through. The step must contain `intersectionsIncludingUpcomingManeuverIntersection` otherwise this property will be `nil`. */ - @objc public var upcomingIntersection: Intersection? { + public var upcomingIntersection: Intersection? { guard let intersections = intersectionsIncludingUpcomingManeuverIntersection, intersections.startIndex ..< intersections.endIndex - 1 ~= intersectionIndex else { return nil } @@ -462,14 +448,14 @@ open class RouteStepProgress: NSObject { /** Index representing the current intersection. */ - @objc public var intersectionIndex: Int = 0 + public var intersectionIndex: Int = 0 /** The current intersection the user will travel through. The step must contain `intersectionsIncludingUpcomingManeuverIntersection` otherwise this property will be `nil`. */ - @objc public var currentIntersection: Intersection? { + public var currentIntersection: Intersection? { guard let intersections = intersectionsIncludingUpcomingManeuverIntersection, intersections.startIndex ..< intersections.endIndex ~= intersectionIndex else { return nil } @@ -480,7 +466,7 @@ open class RouteStepProgress: NSObject { /** Returns an array of the calculated distances from the current intersection to the next intersection on the current step. */ - @objc public var intersectionDistances: [CLLocationDistance]? + public var intersectionDistances: [CLLocationDistance]? /** The distance in meters the user is to the next intersection they will pass through. @@ -490,12 +476,12 @@ open class RouteStepProgress: NSObject { /** Index into `step.instructionsDisplayedAlongStep` representing the current visual instruction for the step. */ - @objc public var visualInstructionIndex: Int = 0 + public var visualInstructionIndex: Int = 0 /** An `Array` of remaining `VisualInstruction` for a step. */ - @objc public var remainingVisualInstructions: [VisualInstructionBanner]? { + public var remainingVisualInstructions: [VisualInstructionBanner]? { guard let visualInstructions = step.instructionsDisplayedAlongStep else { return nil } return Array(visualInstructions.suffix(from: self.visualInstructionIndex)) } @@ -503,12 +489,12 @@ open class RouteStepProgress: NSObject { /** Index into `step.instructionsSpokenAlongStep` representing the current spoken instruction. */ - @objc public var spokenInstructionIndex: Int = 0 + public var spokenInstructionIndex: Int = 0 /** An `Array` of remaining `SpokenInstruction` for a step. */ - @objc public var remainingSpokenInstructions: [SpokenInstruction]? { + public var remainingSpokenInstructions: [SpokenInstruction]? { guard let instructions = step.instructionsSpokenAlongStep, spokenInstructionIndex <= instructions.endIndex @@ -519,7 +505,7 @@ open class RouteStepProgress: NSObject { /** Current spoken instruction for the user's progress along a step. */ - @objc public var currentSpokenInstruction: SpokenInstruction? { + public var currentSpokenInstruction: SpokenInstruction? { guard let instructionsSpokenAlongStep = step.instructionsSpokenAlongStep else { return nil } guard self.spokenInstructionIndex < instructionsSpokenAlongStep.count else { return nil } return instructionsSpokenAlongStep[self.spokenInstructionIndex] @@ -528,7 +514,7 @@ open class RouteStepProgress: NSObject { /** Current visual instruction for the user's progress along a step. */ - @objc public var currentVisualInstruction: VisualInstructionBanner? { + public var currentVisualInstruction: VisualInstructionBanner? { guard let instructionsDisplayedAlongStep = step.instructionsDisplayedAlongStep else { return nil } guard self.visualInstructionIndex < instructionsDisplayedAlongStep.count else { return nil } return instructionsDisplayedAlongStep[self.visualInstructionIndex] diff --git a/MapboxCoreNavigation/SimulatedLocationManager.swift b/MapboxCoreNavigation/SimulatedLocationManager.swift index c66a7bfa..502e147f 100644 --- a/MapboxCoreNavigation/SimulatedLocationManager.swift +++ b/MapboxCoreNavigation/SimulatedLocationManager.swift @@ -23,7 +23,7 @@ private class SimulatedLocation: CLLocation { } /// Provides methods for modifying the locations along the `SimulatedLocationManager`'s route. -@objc public protocol SimulatedLocationManagerDelegate: AnyObject { +public protocol SimulatedLocationManagerDelegate: AnyObject { /// Offers the delegate an opportunity to modify the next location along the route. This can be useful when testing re-routing. /// /// - Parameters: @@ -41,7 +41,6 @@ private class SimulatedLocation: CLLocation { /// } /// } /// ``` - @objc func simulatedLocationManager(_ simulatedLocationManager: SimulatedLocationManager, locationFor originalLocation: CLLocation) -> CLLocation } @@ -50,7 +49,6 @@ private class SimulatedLocation: CLLocation { The route will be replaced upon a `RouteControllerDidReroute` notification. */ -@objc(MBSimulatedLocationManager) open class SimulatedLocationManager: NavigationLocationManager { fileprivate var currentDistance: CLLocationDistance = 0 fileprivate var currentLocation = CLLocation() @@ -62,12 +60,12 @@ open class SimulatedLocationManager: NavigationLocationManager { /** Specify the multiplier to use when calculating speed based on the RouteLeg’s `expectedSegmentTravelTimes`. */ - @objc public var speedMultiplier: Double = 1 + public var speedMultiplier: Double = 1 /// Instead of following the given route, go slightly off route. Useful for testing rerouting. - @objc public weak var simulatedLocationManagerDelegate: SimulatedLocationManagerDelegate? + public weak var simulatedLocationManagerDelegate: SimulatedLocationManagerDelegate? - @objc override open var location: CLLocation? { + override open var location: CLLocation? { self.currentLocation } @@ -96,7 +94,7 @@ open class SimulatedLocationManager: NavigationLocationManager { - parameter route: The initial route. - returns: A `SimulatedLocationManager` */ - @objc public init(route: Route) { + public init(route: Route) { super.init() self.initializeSimulatedLocationManager(for: route, currentDistance: 0, currentSpeed: 30) } @@ -107,7 +105,7 @@ open class SimulatedLocationManager: NavigationLocationManager { - parameter routeProgress: The routeProgress of the current route. - returns: A `SimulatedLocationManager` */ - @objc public init(routeProgress: RouteProgress) { + public init(routeProgress: RouteProgress) { super.init() let currentDistance = self.calculateCurrentDistance(routeProgress.distanceTraveled) self.initializeSimulatedLocationManager(for: routeProgress.route, currentDistance: currentDistance, currentSpeed: 0) @@ -123,21 +121,24 @@ open class SimulatedLocationManager: NavigationLocationManager { } private func reset() { - if let coordinates = route?.coordinates { - self.routeLine = coordinates - self.locations = coordinates.simulatedLocationsWithTurnPenalties() - } + // TODO: fix me + // if let coordinates = self.route?.coordinates { +// self.routeLine = coordinates +// self.locations = coordinates.simulatedLocationsWithTurnPenalties() +// } } private func calculateCurrentDistance(_ distance: CLLocationDistance) -> CLLocationDistance { distance + (self.currentSpeed * self.speedMultiplier) } - @objc private func progressDidChange(_ notification: Notification) { + @objc + private func progressDidChange(_ notification: Notification) { self.routeProgress = notification.userInfo![RouteControllerNotificationUserInfoKey.routeProgressKey] as? RouteProgress } - @objc private func didReroute(_ notification: Notification) { + @objc + private func didReroute(_ notification: Notification) { guard let routeController = notification.object as? RouteController else { return } @@ -160,29 +161,30 @@ open class SimulatedLocationManager: NavigationLocationManager { } } - @objc fileprivate func tick() { + @objc + fileprivate func tick() { NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.tick), object: nil) - let polyline = Polyline(routeLine) + let polyline = LineString(self.routeLine) - guard let newCoordinate = polyline.coordinateFromStart(distance: currentDistance) else { + guard let newCoordinate = polyline.coordinateFromStart(distance: self.currentDistance) else { return } // Closest coordinate ahead - guard let lookAheadCoordinate = polyline.coordinateFromStart(distance: currentDistance + 10) else { return } + guard let lookAheadCoordinate = polyline.coordinateFromStart(distance: self.currentDistance + 10) else { return } guard let closestCoordinate = polyline.closestCoordinate(to: newCoordinate) else { return } let closestLocation = self.locations[closestCoordinate.index] let distanceToClosest = closestLocation.distance(from: CLLocation(newCoordinate)) let distance = min(max(distanceToClosest, 10), safeDistance) - let coordinatesNearby = polyline.trimmed(from: newCoordinate, distance: 100).coordinates + let coordinatesNearby = polyline.trimmed(from: newCoordinate, distance: 100)?.coordinates // Simulate speed based on expected segment travel time if let expectedSegmentTravelTimes = routeProgress?.currentLeg.expectedSegmentTravelTimes, - let coordinates = routeProgress?.route.coordinates, - let closestCoordinateOnRoute = Polyline(routeProgress!.route.coordinates!).closestCoordinate(to: newCoordinate), + let coordinates = self.routeProgress?.route.shape?.coordinates, + let closestCoordinateOnRoute = LineString(routeProgress!.route.shape!.coordinates).closestCoordinate(to: newCoordinate), let nextCoordinateOnRoute = coordinates.after(element: coordinates[closestCoordinateOnRoute.index]), let time = expectedSegmentTravelTimes.optional[closestCoordinateOnRoute.index] { let distance = coordinates[closestCoordinateOnRoute.index].distance(to: nextCoordinateOnRoute) diff --git a/MapboxNavigation/CarPlayManager.swift b/MapboxNavigation/CarPlayManager.swift index 4e315829..12b2a03b 100644 --- a/MapboxNavigation/CarPlayManager.swift +++ b/MapboxNavigation/CarPlayManager.swift @@ -27,8 +27,7 @@ public enum CarPlayActivity: Int { Implement this protocol and assign an instance to the `delegate` property of the shared instance of `CarPlayManager`. */ @available(iOS 12.0, *) -@objc(MBCarPlayManagerDelegate) -public protocol CarPlayManagerDelegate { +public protocol CarPlayManagerDelegate: AnyObject { /** Offers the delegate an opportunity to provide a customized list of leading bar buttons. @@ -41,8 +40,7 @@ public protocol CarPlayManagerDelegate { - parameter activity: What the user is currently doing on the CarPlay screen. Use this parameter to distinguish between multiple templates of the same kind, such as multiple `CPMapTemplate`s. - returns: An array of bar buttons to display on the leading side of the navigation bar while `template` is visible. */ - @objc(carPlayManager:leadingNavigationBarButtonsWithTraitCollection:inTemplate:forActivity:) - optional func carPlayManager(_ carPlayManager: CarPlayManager, leadingNavigationBarButtonsCompatibleWith traitCollection: UITraitCollection, in template: CPTemplate, for activity: CarPlayActivity) -> [CPBarButton]? + func carPlayManager(_ carPlayManager: CarPlayManager, leadingNavigationBarButtonsCompatibleWith traitCollection: UITraitCollection, in template: CPTemplate, for activity: CarPlayActivity) -> [CPBarButton]? /** Offers the delegate an opportunity to provide a customized list of trailing bar buttons. @@ -55,8 +53,7 @@ public protocol CarPlayManagerDelegate { - parameter activity: What the user is currently doing on the CarPlay screen. Use this parameter to distinguish between multiple templates of the same kind, such as multiple `CPMapTemplate`s. - returns: An array of bar buttons to display on the trailing side of the navigation bar while `template` is visible. */ - @objc(carPlayManager:trailingNavigationBarButtonsWithTraitCollection:inTemplate:forActivity:) - optional func carPlayManager(_ carPlayManager: CarPlayManager, trailingNavigationBarButtonsCompatibleWith traitCollection: UITraitCollection, in template: CPTemplate, for activity: CarPlayActivity) -> [CPBarButton]? + func carPlayManager(_ carPlayManager: CarPlayManager, trailingNavigationBarButtonsCompatibleWith traitCollection: UITraitCollection, in template: CPTemplate, for activity: CarPlayActivity) -> [CPBarButton]? /** Offers the delegate an opportunity to provide a customized list of buttons displayed on the map. @@ -70,8 +67,7 @@ public protocol CarPlayManagerDelegate { - parameter activity: What the user is currently doing on the CarPlay screen. Use this parameter to distinguish between multiple templates of the same kind, such as multiple `CPMapTemplate`s. - returns: An array of map buttons to display on the map while `template` is visible. */ - @objc(carPlayManager:mapButtonsCompatibleWithTraitCollection:inTemplate:forActivity:) - optional func carPlayManager(_ carplayManager: CarPlayManager, mapButtonsCompatibleWith traitCollection: UITraitCollection, in template: CPTemplate, for activity: CarPlayActivity) -> [CPMapButton]? + func carPlayManager(_ carplayManager: CarPlayManager, mapButtonsCompatibleWith traitCollection: UITraitCollection, in template: CPTemplate, for activity: CarPlayActivity) -> [CPMapButton]? /** Offers the delegate an opportunity to provide an alternate navigator, otherwise a default built-in RouteController will be created and used. @@ -80,8 +76,7 @@ public protocol CarPlayManagerDelegate { - parameter route: The route for which the returned route controller will manage location updates. - returns: A route controller that manages location updates along `route`. */ - @objc(carPlayManager:routeControllerAlongRoute:) - optional func carPlayManager(_ carPlayManager: CarPlayManager, routeControllerAlong route: Route) -> RouteController + func carPlayManager(_ carPlayManager: CarPlayManager, routeControllerAlong route: Route) -> RouteController /** Offers the delegate an opportunity to react to updates in the search text. @@ -93,8 +88,7 @@ public protocol CarPlayManagerDelegate { - postcondition: You must call `completionHandler` within this method. */ - @objc(carPlayManager:searchTemplate:updatedSearchText:completionHandler:) - optional func carPlayManager(_ carPlayManager: CarPlayManager, searchTemplate: CPSearchTemplate, updatedSearchText searchText: String, completionHandler: @escaping ([CPListItem]) -> Void) + func carPlayManager(_ carPlayManager: CarPlayManager, searchTemplate: CPSearchTemplate, updatedSearchText searchText: String, completionHandler: @escaping ([CPListItem]) -> Void) /** Offers the delegate an opportunity to react to selection of a search result. @@ -106,8 +100,7 @@ public protocol CarPlayManagerDelegate { - postcondition: You must call `completionHandler` within this method. */ - @objc(carPlayManager:searchTemplate:selectedResult:completionHandler:) - optional func carPlayManager(_ carPlayManager: CarPlayManager, searchTemplate: CPSearchTemplate, selectedResult item: CPListItem, completionHandler: @escaping () -> Void) + func carPlayManager(_ carPlayManager: CarPlayManager, searchTemplate: CPSearchTemplate, selectedResult item: CPListItem, completionHandler: @escaping () -> Void) /** Called when navigation begins so that the containing app can update accordingly. @@ -115,7 +108,6 @@ public protocol CarPlayManagerDelegate { - parameter carPlayManager: The shared CarPlay manager. - parameter routeController: The route controller that has begun managing location updates for a navigation session. */ - @objc(carPlayManager:didBeginNavigationWithRouteController:) func carPlayManager(_ carPlayManager: CarPlayManager, didBeginNavigationWith routeController: RouteController) /** @@ -123,7 +115,7 @@ public protocol CarPlayManagerDelegate { - parameter carPlayManager: The shared CarPlay manager. */ - @objc func carPlayManagerDidEndNavigation(_ carPlayManager: CarPlayManager) + func carPlayManagerDidEndNavigation(_ carPlayManager: CarPlayManager) /** Called when the carplay manager will disable the idle timer. @@ -133,7 +125,24 @@ public protocol CarPlayManagerDelegate { - parameter carPlayManager: The shared CarPlay manager. - returns: A Boolean value indicating whether to disable idle timer when carplay is connected and enable when disconnected. */ - @objc optional func carplayManagerShouldDisableIdleTimer(_ carPlayManager: CarPlayManager) -> Bool + func carplayManagerShouldDisableIdleTimer(_ carPlayManager: CarPlayManager) -> Bool +} + +extension CarPlayManagerDelegate { + func carPlayManager(_ carPlayManager: CarPlayManager, leadingNavigationBarButtonsCompatibleWith traitCollection: UITraitCollection, in template: CPTemplate, for activity: CarPlayActivity) -> [CPBarButton]? {} + func carPlayManager(_ carPlayManager: CarPlayManager, trailingNavigationBarButtonsCompatibleWith traitCollection: UITraitCollection, in template: CPTemplate, for activity: CarPlayActivity) -> [CPBarButton]? {} + func carPlayManager(_ carplayManager: CarPlayManager, mapButtonsCompatibleWith traitCollection: UITraitCollection, in template: CPTemplate, for activity: CarPlayActivity) -> [CPMapButton]? {} + func carPlayManager(_ carPlayManager: CarPlayManager, routeControllerAlong route: Route) -> RouteController { + RouteController(along: route) + } + + func carPlayManager(_ carPlayManager: CarPlayManager, searchTemplate: CPSearchTemplate, updatedSearchText searchText: String, completionHandler: @escaping ([CPListItem]) -> Void) {} + func carPlayManager(_ carPlayManager: CarPlayManager, searchTemplate: CPSearchTemplate, selectedResult item: CPListItem, completionHandler: @escaping () -> Void) {} + func carPlayManager(_ carPlayManager: CarPlayManager, didBeginNavigationWith routeController: RouteController) {} + func carPlayManagerDidEndNavigation(_ carPlayManager: CarPlayManager) {} + func carplayManagerShouldDisableIdleTimer(_ carPlayManager: CarPlayManager) -> Bool { + true + } } /** @@ -144,7 +153,6 @@ public protocol CarPlayManagerDelegate { Messages declared in the `CPApplicationDelegate` protocol should be sent to this object in the containing application's application delegate. Implement `CarPlayManagerDelegate` in the containing application and assign an instance to the `delegate` property of the `CarPlayManager` shared instance. */ @available(iOS 12.0, *) -@objc(MBCarPlayManager) public class CarPlayManager: NSObject { public fileprivate(set) var interfaceController: CPInterfaceController? public fileprivate(set) var carWindow: UIWindow? @@ -153,17 +161,17 @@ public class CarPlayManager: NSObject { /** Developers should assign their own object as a delegate implementing the CarPlayManagerDelegate protocol for customization. */ - @objc public weak var delegate: CarPlayManagerDelegate? + public weak var delegate: CarPlayManagerDelegate? /** If set to `true`, turn-by-turn directions will simulate the user traveling along the selected route when initiated from CarPlay. */ - @objc public var simulatesLocations = false + public var simulatesLocations = false /** This property specifies a multiplier to be applied to the user's speed in simulation mode. */ - @objc public var simulatedSpeedMultiplier = 1.0 + public var simulatedSpeedMultiplier = 1.0 /** The shared CarPlay manager. @@ -194,7 +202,7 @@ public class CarPlayManager: NSObject { /** A Boolean value indicating whether the phone is connected to CarPlay. */ - @objc public var isConnectedToCarPlay = false + public var isConnectedToCarPlay = false lazy var fullDateComponentsFormatter: DateComponentsFormatter = { let formatter = DateComponentsFormatter() @@ -227,7 +235,7 @@ extension CarPlayManager: CPApplicationDelegate { interfaceController.delegate = self self.interfaceController = interfaceController - if let shouldDisableIdleTimer = delegate?.carplayManagerShouldDisableIdleTimer?(self) { + if let shouldDisableIdleTimer = self.delegate?.carplayManagerShouldDisableIdleTimer(self) { UIApplication.shared.isIdleTimerDisabled = shouldDisableIdleTimer } else { UIApplication.shared.isIdleTimerDisabled = true @@ -247,7 +255,7 @@ extension CarPlayManager: CPApplicationDelegate { self.interfaceController = nil self.carWindow?.isHidden = true - if let shouldDisableIdleTimer = delegate?.carplayManagerShouldDisableIdleTimer?(self) { + if let shouldDisableIdleTimer = self.delegate?.carplayManagerShouldDisableIdleTimer(self) { UIApplication.shared.isIdleTimerDisabled = !shouldDisableIdleTimer } else { UIApplication.shared.isIdleTimerDisabled = false @@ -260,7 +268,7 @@ extension CarPlayManager: CPApplicationDelegate { let mapTemplate = CPMapTemplate() mapTemplate.mapDelegate = self - if let leadingButtons = delegate?.carPlayManager?(self, leadingNavigationBarButtonsCompatibleWith: traitCollection, in: mapTemplate, for: .browsing) { + if let leadingButtons = self.delegate?.carPlayManager(self, leadingNavigationBarButtonsCompatibleWith: traitCollection, in: mapTemplate, for: .browsing) { mapTemplate.leadingNavigationBarButtons = leadingButtons } else { #if canImport(CarPlay) && canImport(MapboxGeocoder) @@ -272,11 +280,11 @@ extension CarPlayManager: CPApplicationDelegate { #endif } - if let trailingButtons = delegate?.carPlayManager?(self, trailingNavigationBarButtonsCompatibleWith: traitCollection, in: mapTemplate, for: .browsing) { + if let trailingButtons = self.delegate?.carPlayManager(self, trailingNavigationBarButtonsCompatibleWith: traitCollection, in: mapTemplate, for: .browsing) { mapTemplate.trailingNavigationBarButtons = trailingButtons } - if let mapButtons = delegate?.carPlayManager?(self, mapButtonsCompatibleWith: traitCollection, in: mapTemplate, for: .browsing) { + if let mapButtons = self.delegate?.carPlayManager(self, mapButtonsCompatibleWith: traitCollection, in: mapTemplate, for: .browsing) { mapTemplate.mapButtons = mapButtons } else if let vc = viewController as? CarPlayMapViewController { mapTemplate.mapButtons = [vc.recenterButton, self.panMapButton(for: mapTemplate, traitCollection: traitCollection), vc.zoomInButton(), vc.zoomOutButton()] @@ -404,7 +412,7 @@ extension CarPlayManager: CPListTemplateDelegate { let originWaypoint = fromWaypoint ?? Waypoint(location: location, heading: userLocation.heading, name: name) let routeOptions = NavigationRouteOptions(waypoints: [originWaypoint, toWaypoint]) - Directions.shared.calculate(routeOptions) { [weak self, weak mapTemplate] waypoints, routes, error in + Directions.shared.calculate(routeOptions) { [weak self, weak mapTemplate] _, result in defer { completionHandler() } @@ -412,48 +420,51 @@ extension CarPlayManager: CPListTemplateDelegate { guard let self, let mapTemplate else { return } - if let error { + switch result { + case let .failure(error): let okTitle = NSLocalizedString("CARPLAY_OK", bundle: .mapboxNavigation, value: "OK", comment: "CPNavigationAlert OK button title") let okAction = CPAlertAction(title: okTitle, style: .default) { _ in interfaceController.popToRootTemplate(animated: true) } let alert = CPNavigationAlert(titleVariants: [error.localizedDescription], - subtitleVariants: [error.localizedFailureReason ?? ""], + subtitleVariants: [error.failureReason ?? ""], imageSet: nil, primaryAction: okAction, secondaryAction: nil, duration: 0) mapTemplate.present(navigationAlert: alert, animated: true) + + case let .success(response): + guard let waypoints = response.waypoints, let routes = response.routes else { + return + } + + let routeChoices = routes.map { route -> CPRouteChoice in + let summaryVariants = [ + self.fullDateComponentsFormatter.string(from: route.expectedTravelTime)!, + self.shortDateComponentsFormatter.string(from: route.expectedTravelTime)!, + self.briefDateComponentsFormatter.string(from: route.expectedTravelTime)! + ] + let routeChoice = CPRouteChoice(summaryVariants: summaryVariants, additionalInformationVariants: [route.description], selectionSummaryVariants: [route.description]) + routeChoice.userInfo = route + return routeChoice + } + + let originPlacemark = MKPlacemark(coordinate: waypoints.first!.coordinate) + let destinationPlacemark = MKPlacemark(coordinate: waypoints.last!.coordinate, addressDictionary: ["street": waypoints.last!.name ?? ""]) + let trip = CPTrip(origin: MKMapItem(placemark: originPlacemark), destination: MKMapItem(placemark: destinationPlacemark), routeChoices: routeChoices) + trip.userInfo = routeOptions + + let goTitle = NSLocalizedString("CARPLAY_GO", bundle: .mapboxNavigation, value: "Go", comment: "Title for start button in CPTripPreviewTextConfiguration") + let alternativeRoutesTitle = NSLocalizedString("CARPLAY_MORE_ROUTES", bundle: .mapboxNavigation, value: "More Routes", comment: "Title for alternative routes in CPTripPreviewTextConfiguration") + let overviewTitle = NSLocalizedString("CARPLAY_OVERVIEW", bundle: .mapboxNavigation, value: "Overview", comment: "Title for overview button in CPTripPreviewTextConfiguration") + let defaultPreviewText = CPTripPreviewTextConfiguration(startButtonTitle: goTitle, additionalRoutesButtonTitle: alternativeRoutesTitle, overviewButtonTitle: overviewTitle) + + let previewMapTemplate = self.mapTemplate(forPreviewing: trip) + interfaceController.pushTemplate(previewMapTemplate, animated: true) + + previewMapTemplate.showTripPreviews([trip], textConfiguration: defaultPreviewText) } - guard let waypoints, let routes else { - return - } - - let routeChoices = routes.map { route -> CPRouteChoice in - let summaryVariants = [ - self.fullDateComponentsFormatter.string(from: route.expectedTravelTime)!, - self.shortDateComponentsFormatter.string(from: route.expectedTravelTime)!, - self.briefDateComponentsFormatter.string(from: route.expectedTravelTime)! - ] - let routeChoice = CPRouteChoice(summaryVariants: summaryVariants, additionalInformationVariants: [route.description], selectionSummaryVariants: [route.description]) - routeChoice.userInfo = route - return routeChoice - } - - let originPlacemark = MKPlacemark(coordinate: waypoints.first!.coordinate) - let destinationPlacemark = MKPlacemark(coordinate: waypoints.last!.coordinate, addressDictionary: ["street": waypoints.last!.name ?? ""]) - let trip = CPTrip(origin: MKMapItem(placemark: originPlacemark), destination: MKMapItem(placemark: destinationPlacemark), routeChoices: routeChoices) - trip.userInfo = routeOptions - - let goTitle = NSLocalizedString("CARPLAY_GO", bundle: .mapboxNavigation, value: "Go", comment: "Title for start button in CPTripPreviewTextConfiguration") - let alternativeRoutesTitle = NSLocalizedString("CARPLAY_MORE_ROUTES", bundle: .mapboxNavigation, value: "More Routes", comment: "Title for alternative routes in CPTripPreviewTextConfiguration") - let overviewTitle = NSLocalizedString("CARPLAY_OVERVIEW", bundle: .mapboxNavigation, value: "Overview", comment: "Title for overview button in CPTripPreviewTextConfiguration") - let defaultPreviewText = CPTripPreviewTextConfiguration(startButtonTitle: goTitle, additionalRoutesButtonTitle: alternativeRoutesTitle, overviewButtonTitle: overviewTitle) - - let previewMapTemplate = self.mapTemplate(forPreviewing: trip) - interfaceController.pushTemplate(previewMapTemplate, animated: true) - - previewMapTemplate.showTripPreviews([trip], textConfiguration: defaultPreviewText) } } @@ -461,10 +472,10 @@ extension CarPlayManager: CPListTemplateDelegate { let rootViewController = self.carWindow?.rootViewController as! CarPlayMapViewController let mapTemplate = CPMapTemplate() mapTemplate.mapDelegate = self - if let leadingButtons = delegate?.carPlayManager?(self, leadingNavigationBarButtonsCompatibleWith: rootViewController.traitCollection, in: mapTemplate, for: .previewing) { + if let leadingButtons = self.delegate?.carPlayManager(self, leadingNavigationBarButtonsCompatibleWith: rootViewController.traitCollection, in: mapTemplate, for: .previewing) { mapTemplate.leadingNavigationBarButtons = leadingButtons } - if let trailingButtons = delegate?.carPlayManager?(self, trailingNavigationBarButtonsCompatibleWith: rootViewController.traitCollection, in: mapTemplate, for: .previewing) { + if let trailingButtons = self.delegate?.carPlayManager(self, trailingNavigationBarButtonsCompatibleWith: rootViewController.traitCollection, in: mapTemplate, for: .previewing) { mapTemplate.trailingNavigationBarButtons = trailingButtons } return mapTemplate @@ -484,7 +495,7 @@ extension CarPlayManager: CPMapTemplateDelegate { mapTemplate.hideTripPreviews() let route = routeChoice.userInfo as! Route - let routeController: RouteController = if let routeControllerFromDelegate = delegate?.carPlayManager?(self, routeControllerAlong: route) { + let routeController: RouteController = if let routeControllerFromDelegate = self.delegate?.carPlayManager(self, routeControllerAlong: route) { routeControllerFromDelegate } else { self.createRouteController(with: route) @@ -534,7 +545,7 @@ extension CarPlayManager: CPMapTemplateDelegate { mapTemplate.mapButtons = [overviewButton, showFeedbackButton] if let rootViewController = carWindow?.rootViewController as? CarPlayMapViewController, - let leadingButtons = delegate?.carPlayManager?(self, leadingNavigationBarButtonsCompatibleWith: rootViewController.traitCollection, in: mapTemplate, for: .navigating) { + let leadingButtons = self.delegate?.carPlayManager(self, leadingNavigationBarButtonsCompatibleWith: rootViewController.traitCollection, in: mapTemplate, for: .navigating) { mapTemplate.leadingNavigationBarButtons = leadingButtons } @@ -549,7 +560,7 @@ extension CarPlayManager: CPMapTemplateDelegate { mapTemplate.leadingNavigationBarButtons.insert(muteButton, at: 0) if let rootViewController = carWindow?.rootViewController as? CarPlayMapViewController, - let trailingButtons = delegate?.carPlayManager?(self, trailingNavigationBarButtonsCompatibleWith: rootViewController.traitCollection, in: mapTemplate, for: .navigating) { + let trailingButtons = self.delegate?.carPlayManager(self, trailingNavigationBarButtonsCompatibleWith: rootViewController.traitCollection, in: mapTemplate, for: .navigating) { mapTemplate.trailingNavigationBarButtons = trailingButtons } let exitButton = CPBarButton(type: .text) { [weak self] (_: CPBarButton) in @@ -714,11 +725,11 @@ class CarPlayManager: NSObject { /** The shared CarPlay manager. */ - @objc public static var shared = CarPlayManager() + public static var shared = CarPlayManager() /** A Boolean value indicating whether the phone is connected to CarPlay. */ - @objc public var isConnectedToCarPlay = false + public var isConnectedToCarPlay = false } #endif diff --git a/MapboxNavigation/CarPlayNavigationViewController.swift b/MapboxNavigation/CarPlayNavigationViewController.swift index cf4365bd..2ff7efc5 100644 --- a/MapboxNavigation/CarPlayNavigationViewController.swift +++ b/MapboxNavigation/CarPlayNavigationViewController.swift @@ -11,14 +11,13 @@ import CarPlay - seealso: NavigationViewController */ @available(iOS 12.0, *) -@objc(MBCarPlayNavigationViewController) public class CarPlayNavigationViewController: UIViewController, MLNMapViewDelegate { /** The view controller’s delegate. */ - @objc public weak var carPlayNavigationDelegate: CarPlayNavigationDelegate? + public weak var carPlayNavigationDelegate: CarPlayNavigationDelegate? - @objc public var drivingSide: DrivingSide = .right + public var drivingSide: DrivingSide = .right var routeController: RouteController var mapView: NavigationMapView? @@ -149,7 +148,7 @@ public class CarPlayNavigationViewController: UIViewController, MLNMapViewDelega /** Shows the interface for providing feedback about the route. */ - @objc public func showFeedback() { + public func showFeedback() { self.carInterfaceController.pushTemplate(self.carFeedbackTemplate, animated: true) } @@ -158,7 +157,7 @@ public class CarPlayNavigationViewController: UIViewController, MLNMapViewDelega When this property is true, the map follows the user’s location and rotates when their course changes. Otherwise, the map shows an overview of the route. */ - @objc public var tracksUserCourse: Bool { + public var tracksUserCourse: Bool { get { self.mapView?.tracksUserCourse ?? false } @@ -173,7 +172,7 @@ public class CarPlayNavigationViewController: UIViewController, MLNMapViewDelega return } self.mapView?.enableFrameByFrameCourseViewTracking(for: 3) - self.mapView?.setOverheadCameraView(from: userLocation, along: self.routeController.routeProgress.route.coordinates!, for: self.edgePadding) + self.mapView?.setOverheadCameraView(from: userLocation, along: self.routeController.routeProgress.route.shape!.coordinates, for: self.edgePadding) } } } @@ -366,7 +365,7 @@ extension CarPlayNavigationViewController: StyleManagerDelegate { @available(iOS 12.0, *) extension CarPlayNavigationViewController: RouteControllerDelegate { - public func routeController(_ routeController: RouteController, didArriveAt waypoint: Waypoint) -> Bool { + public func routeController(_ routeController: MapboxCoreNavigation.RouteController, didArriveAt waypoint: MapboxDirections.Waypoint) -> Bool { if routeController.routeProgress.isFinalLeg { self.presentArrivalUI() self.carPlayNavigationDelegate?.carPlayNavigationViewControllerDidArrive(self) @@ -375,6 +374,32 @@ extension CarPlayNavigationViewController: RouteControllerDelegate { } return false } + + public func routeController(_ routeController: MapboxCoreNavigation.RouteController, shouldPreventReroutesWhenArrivingAt waypoint: MapboxDirections.Waypoint) -> Bool { + false + } + + public func routeController(_ routeController: MapboxCoreNavigation.RouteController, shouldRerouteFrom location: CLLocation) -> Bool { + false + } + + public func routeController(_ routeController: MapboxCoreNavigation.RouteController, willRerouteFrom location: CLLocation) {} + + public func routeController(_ routeController: MapboxCoreNavigation.RouteController, shouldDiscard location: CLLocation) -> Bool { + false + } + + public func routeController(_ routeController: MapboxCoreNavigation.RouteController, didRerouteAlong route: MapboxDirections.Route, reason: MapboxCoreNavigation.RouteController.RerouteReason) {} + + public func routeController(_ routeController: MapboxCoreNavigation.RouteController, didFailToRerouteWith error: any Error) {} + + public func routeController(_ routeController: MapboxCoreNavigation.RouteController, didUpdate locations: [CLLocation]) {} + + public func routeControllerShouldDisableBatteryMonitoring(_ routeController: MapboxCoreNavigation.RouteController) -> Bool { + false + } + + public func routeControllerGetDirections(from location: CLLocation, along progress: MapboxCoreNavigation.RouteProgress, completion: @escaping (MapboxDirections.Route?, [MapboxDirections.Route]?, (any Error)?) -> Void) {} } /** @@ -397,6 +422,6 @@ public protocol CarPlayNavigationDelegate { - parameter carPlayNavigationViewController: The CarPlay navigation view controller that was dismissed. */ - @objc func carPlayNavigationViewControllerDidArrive(_ carPlayNavigationViewController: CarPlayNavigationViewController) + func carPlayNavigationViewControllerDidArrive(_ carPlayNavigationViewController: CarPlayNavigationViewController) } #endif diff --git a/MapboxNavigation/InstructionLabel.swift b/MapboxNavigation/InstructionLabel.swift index f0b23adf..34658fd4 100644 --- a/MapboxNavigation/InstructionLabel.swift +++ b/MapboxNavigation/InstructionLabel.swift @@ -3,7 +3,6 @@ import MapboxDirections import UIKit /// :nodoc: -@objc(MBInstructionLabel) open class InstructionLabel: StylableLabel, InstructionPresenterDataSource { typealias AvailableBoundsHandler = () -> (CGRect) var availableBounds: AvailableBoundsHandler! @@ -29,7 +28,7 @@ open class InstructionLabel: StylableLabel, InstructionPresenterDataSource { let presenter = InstructionPresenter(instruction, dataSource: self, imageRepository: imageRepository, downloadCompletion: update) let attributed = presenter.attributedText() - attributedText = self.instructionDelegate?.label?(self, willPresent: instruction, as: attributed) ?? attributed + attributedText = self.instructionDelegate?.label(self, willPresent: instruction, as: attributed) ?? attributed self.instructionPresenter = presenter } } @@ -40,7 +39,6 @@ open class InstructionLabel: StylableLabel, InstructionPresenterDataSource { /** The `VoiceControllerDelegate` protocol defines a method that allows an object to customize presented visual instructions. */ -@objc(MBVisualInstructionDelegate) public protocol VisualInstructionDelegate: AnyObject { /** Called when an InstructionLabel will present a visual instruction. @@ -50,6 +48,11 @@ public protocol VisualInstructionDelegate: AnyObject { - parameter presented: the formatted string that is provided by the instruction presenter - returns: optionally, a customized NSAttributedString that will be presented instead of the default, or if nil, the default behavior will be used. */ - @objc(label:willPresentVisualInstruction:asAttributedString:) - optional func label(_ label: InstructionLabel, willPresent instruction: VisualInstruction, as presented: NSAttributedString) -> NSAttributedString? + func label(_ label: InstructionLabel, willPresent instruction: VisualInstruction, as presented: NSAttributedString) -> NSAttributedString? +} + +extension VisualInstructionDelegate { + func label(_ label: InstructionLabel, willPresent instruction: VisualInstruction, as presented: NSAttributedString) -> NSAttributedString? { + nil + } } diff --git a/MapboxNavigation/InstructionPresenter.swift b/MapboxNavigation/InstructionPresenter.swift index c50b8ba8..94df1e23 100644 --- a/MapboxNavigation/InstructionPresenter.swift +++ b/MapboxNavigation/InstructionPresenter.swift @@ -38,19 +38,34 @@ class InstructionPresenter { guard let source = dataSource else { return [] } var attributedPairs = attributedPairs(for: instruction, dataSource: source, imageRepository: imageRepository, onImageDownload: completeShieldDownload) let availableBounds = source.availableBounds() - let totalWidth: CGFloat = attributedPairs.attributedStrings.map { $0.size() }.reduce(.zero, +).width + let totalWidth: CGFloat = attributedPairs.attributedStrings.map { $0.size() }.reduce(CGSize.zero, +).width let stringFits = totalWidth <= availableBounds.width guard !stringFits else { return attributedPairs.attributedStrings } let indexedComponents: [IndexedVisualInstructionComponent] = attributedPairs.components.enumerated().map { IndexedVisualInstructionComponent(component: $1, index: $0) } - let filtered = indexedComponents.filter { $0.component.abbreviation != nil } - let sorted = filtered.sorted { $0.component.abbreviationPriority < $1.component.abbreviationPriority } + let filtered = indexedComponents.filter { + switch $0.component { + case let .text(text): + text.abbreviation != nil + default: + false + } + } + let sorted = filtered.sorted { + switch ($0.component, $1.component) { + case let (.text(text0), .text(text1)): + text0.abbreviationPriority ?? 0 < text1.abbreviationPriority ?? 0 + default: + false + } + } + for component in sorted { let isFirst = component.index == 0 let joinChar = isFirst ? "" : " " - guard component.component.type == .text else { continue } - guard let abbreviation = component.component.abbreviation else { continue } + guard case let .text(text) = component.component else { continue } + guard let abbreviation = text.abbreviation else { continue } attributedPairs.attributedStrings[component.index] = NSAttributedString(string: joinChar + abbreviation, attributes: self.attributes(for: source)) let newWidth: CGFloat = attributedPairs.attributedStrings.map { $0.size() }.reduce(.zero, +).width @@ -63,12 +78,12 @@ class InstructionPresenter { return attributedPairs.attributedStrings } - typealias AttributedInstructionComponents = (components: [VisualInstructionComponent], attributedStrings: [NSAttributedString]) + typealias AttributedInstructionComponents = (components: [VisualInstruction.Component], attributedStrings: [NSAttributedString]) func attributedPairs(for instruction: VisualInstruction, dataSource: DataSource, imageRepository: ImageRepository, onImageDownload: @escaping ImageDownloadCompletion) -> AttributedInstructionComponents { - let components = instruction.components.compactMap { $0 as? VisualInstructionComponent } + let components = instruction.components var strings: [NSAttributedString] = [] - var processedComponents: [VisualInstructionComponent] = [] + var processedComponents: [VisualInstruction.Component] = [] for (index, component) in components.enumerated() { let isFirst = index == 0 @@ -77,18 +92,19 @@ class InstructionPresenter { let initial = NSAttributedString() // This is the closure that builds the string. - let build: (_: VisualInstructionComponent, _: [NSAttributedString]) -> Void = { component, attributedStrings in + let build: (_: VisualInstruction.Component, _: [NSAttributedString]) -> Void = { component, attributedStrings in processedComponents.append(component) strings.append(attributedStrings.reduce(initial, +)) } - let isShield: (_: VisualInstructionComponent?) -> Bool = { component in - guard let key = component?.cacheKey else { return false } - return imageRepository.cachedImageForKey(key) != nil + let isShield: (_: VisualInstruction.Component?) -> Bool = { component in + guard case let .image(image, _) = component else { return false } + + return image.shield != nil } let componentBefore = components.component(before: component) let componentAfter = components.component(after: component) - switch component.type { + switch component { // Throw away exit components. We know this is safe because we know that if there is an exit component, // there is an exit code component, and the latter contains the information we care about. case .exit: @@ -96,7 +112,8 @@ class InstructionPresenter { // If we have a exit, in the first two components, lets handle that. case .exitCode where 0 ... 1 ~= index: - guard let exitString = attributedString(forExitComponent: component, maneuverDirection: instruction.maneuverDirection, dataSource: dataSource) else { fallthrough } + guard let maneuverDirection = instruction.maneuverDirection else { fallthrough } + guard let exitString = self.attributedString(forExitComponent: component, maneuverDirection: maneuverDirection, dataSource: dataSource) else { fallthrough } build(component, [exitString]) // if it's a delimiter, skip it if it's between two shields. @@ -105,9 +122,9 @@ class InstructionPresenter { // If we have an icon component, lets turn it into a shield. case .image: - if let shieldString = attributedString(forShieldComponent: component, repository: imageRepository, dataSource: dataSource, onImageDownload: onImageDownload) { + if let shieldString = self.attributedString(forShieldComponent: component, repository: imageRepository, dataSource: dataSource, onImageDownload: onImageDownload) { build(component, [joinString, shieldString]) - } else if let genericShieldString = attributedString(forGenericShield: component, dataSource: dataSource) { + } else if let genericShieldString = self.attributedString(forGenericShield: component, dataSource: dataSource) { build(component, [joinString, genericShieldString]) } else { fallthrough @@ -115,7 +132,7 @@ class InstructionPresenter { // Otherwise, process as text component. default: - guard let componentString = attributedString(forTextComponent: component, dataSource: dataSource) else { continue } + guard let componentString = self.attributedString(forTextComponent: component, dataSource: dataSource) else { continue } build(component, [joinString, componentString]) } } @@ -124,20 +141,24 @@ class InstructionPresenter { return (components: processedComponents, attributedStrings: strings) } - func attributedString(forExitComponent component: VisualInstructionComponent, maneuverDirection: ManeuverDirection, dataSource: DataSource) -> NSAttributedString? { - guard component.type == .exitCode, let exitCode = component.text else { return nil } + func attributedString(forExitComponent component: VisualInstruction.Component, maneuverDirection: ManeuverDirection, dataSource: DataSource) -> NSAttributedString? { + guard case let .exitCode(exitCode) = component else { return nil } + let side: ExitSide = maneuverDirection == .left ? .left : .right - guard let exitString = exitShield(side: side, text: exitCode, component: component, dataSource: dataSource) else { return nil } + guard let exitString = exitShield(side: side, text: exitCode.text, component: component, dataSource: dataSource) else { return nil } + return exitString } - func attributedString(forGenericShield component: VisualInstructionComponent, dataSource: DataSource) -> NSAttributedString? { - guard component.type == .image, let text = component.text else { return nil } - return self.genericShield(text: text, component: component, dataSource: dataSource) + func attributedString(forGenericShield component: VisualInstruction.Component, dataSource: DataSource) -> NSAttributedString? { + guard case let .image(_, text) = component else { return nil } + + return self.genericShield(text: text.text, component: component, dataSource: dataSource) } - func attributedString(forShieldComponent shield: VisualInstructionComponent, repository: ImageRepository, dataSource: DataSource, onImageDownload: @escaping ImageDownloadCompletion) -> NSAttributedString? { - guard shield.imageURL != nil, let shieldKey = shield.cacheKey else { return nil } + func attributedString(forShieldComponent shield: VisualInstruction.Component, repository: ImageRepository, dataSource: DataSource, onImageDownload: @escaping ImageDownloadCompletion) -> NSAttributedString? { + guard case let .image(image, text) = shield else { return nil } + guard let shieldKey = image.shield?.name else { return nil } // If we have the shield already cached, use that. if let cachedImage = repository.cachedImageForKey(shieldKey) { @@ -151,27 +172,34 @@ class InstructionPresenter { return nil } - func attributedString(forTextComponent component: VisualInstructionComponent, dataSource: DataSource) -> NSAttributedString? { - guard let text = component.text else { return nil } - return NSAttributedString(string: text, attributes: self.attributes(for: dataSource)) + func attributedString(forTextComponent component: VisualInstruction.Component, dataSource: DataSource) -> NSAttributedString? { + guard case let .text(text) = component else { return nil } + + return NSAttributedString(string: text.text, attributes: self.attributes(for: dataSource)) } - private func shieldImageForComponent(_ component: VisualInstructionComponent, in repository: ImageRepository, height: CGFloat, completion: @escaping ImageDownloadCompletion) { - guard let imageURL = component.imageURL, let shieldKey = component.cacheKey else { - return - } - + private func shieldImageForComponent(_ component: VisualInstruction.Component, in repository: ImageRepository, height: CGFloat, completion: @escaping ImageDownloadCompletion) { + guard case let .image(image, text) = component else { return } + guard let shieldKey = image.shield?.name else { return } + guard let imageURL = image.imageURL() else { return } + repository.imageWithURL(imageURL, cacheKey: shieldKey, completion: completion) } private func instructionHasDownloadedAllShields() -> Bool { - let textComponents = self.instruction.components.compactMap { $0 as? VisualInstructionComponent } - guard !textComponents.isEmpty else { return false } - - for component in textComponents { - guard let key = component.cacheKey else { - continue + let imageComponents = self.instruction.components.filter { + switch $0 { + case .image: + true + default: + false } + } + guard !imageComponents.isEmpty else { return false } + + for component in imageComponents { + guard case let .image(image, _) = component else { continue } + guard let key = image.shield?.name else { continue } if self.imageRepository.cachedImageForKey(key) == nil { return false @@ -191,8 +219,9 @@ class InstructionPresenter { return NSAttributedString(attachment: attachment) } - private func genericShield(text: String, component: VisualInstructionComponent, dataSource: DataSource) -> NSAttributedString? { - guard let cacheKey = component.cacheKey else { return nil } + private func genericShield(text: String, component: VisualInstruction.Component, dataSource: DataSource) -> NSAttributedString? { + guard case let .image(image, text) = component else { return nil } + guard let cacheKey = image.shield?.name else { return nil } let additionalKey = GenericRouteShield.criticalHash(dataSource: dataSource) let attachment = GenericShieldAttachment() @@ -201,7 +230,7 @@ class InstructionPresenter { if let image = imageRepository.cachedImageForKey(key) { attachment.image = image } else { - let view = GenericRouteShield(pointSize: dataSource.font.pointSize, text: text) + let view = GenericRouteShield(pointSize: dataSource.font.pointSize, text: text.text) guard let image = takeSnapshot(on: view) else { return nil } self.imageRepository.storeImage(image, forKey: key, toDisk: false) attachment.image = image @@ -212,8 +241,9 @@ class InstructionPresenter { return NSAttributedString(attachment: attachment) } - private func exitShield(side: ExitSide = .right, text: String, component: VisualInstructionComponent, dataSource: DataSource) -> NSAttributedString? { - guard let cacheKey = component.cacheKey else { return nil } + private func exitShield(side: ExitSide = .right, text: String, component: VisualInstruction.Component, dataSource: DataSource) -> NSAttributedString? { + guard case let .image(image, text) = component else { return nil } + guard let cacheKey = image.shield?.name else { return nil } let additionalKey = ExitView.criticalHash(side: side, dataSource: dataSource) let attachment = ExitAttachment() @@ -222,7 +252,7 @@ class InstructionPresenter { if let image = imageRepository.cachedImageForKey(key) { attachment.image = image } else { - let view = ExitView(pointSize: dataSource.font.pointSize, side: side, text: text) + let view = ExitView(pointSize: dataSource.font.pointSize, side: side, text: text.text) guard let image = takeSnapshot(on: view) else { return nil } self.imageRepository.storeImage(image, forKey: key, toDisk: false) attachment.image = image @@ -308,12 +338,12 @@ private extension CGSize { } private struct IndexedVisualInstructionComponent { - let component: Array.Element - let index: Array.Index + let component: Array.Element + let index: Array.Index } -private extension [VisualInstructionComponent] { - func component(before component: VisualInstructionComponent) -> VisualInstructionComponent? { +private extension [VisualInstruction.Component] { + func component(before component: VisualInstruction.Component) -> VisualInstruction.Component? { guard let index = firstIndex(of: component) else { return nil } @@ -323,7 +353,7 @@ private extension [VisualInstructionComponent] { return nil } - func component(after component: VisualInstructionComponent) -> VisualInstructionComponent? { + func component(after component: VisualInstruction.Component) -> VisualInstruction.Component? { guard let index = firstIndex(of: component) else { return nil } diff --git a/MapboxNavigation/InstructionsBannerView.swift b/MapboxNavigation/InstructionsBannerView.swift index 59f4cb5d..c8576b57 100644 --- a/MapboxNavigation/InstructionsBannerView.swift +++ b/MapboxNavigation/InstructionsBannerView.swift @@ -101,7 +101,6 @@ open class BaseInstructionsBannerView: UIControl { /** Updates the instructions banner info with a given `VisualInstructionBanner`. */ - @objc(updateForVisualInstructionBanner:) public func update(for instruction: VisualInstructionBanner?) { let secondaryInstruction = instruction?.secondaryInstruction self.primaryLabel.numberOfLines = secondaryInstruction == nil ? 2 : 1 @@ -121,7 +120,7 @@ open class BaseInstructionsBannerView: UIControl { override open func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() self.maneuverView.isStart = true - let component = VisualInstructionComponent(type: .text, text: "Primary text label", imageURL: nil, abbreviation: nil, abbreviationPriority: NSNotFound) + let component = VisualInstruction.Component.text(text: .init(text: "Primary text label", abbreviation: nil, abbreviationPriority: nil)) let instruction = VisualInstruction(text: nil, maneuverType: .none, maneuverDirection: .none, components: [component]) self.primaryLabel.instruction = instruction diff --git a/MapboxNavigation/LaneView.swift b/MapboxNavigation/LaneView.swift index 55783c47..9722a53a 100644 --- a/MapboxNavigation/LaneView.swift +++ b/MapboxNavigation/LaneView.swift @@ -2,13 +2,12 @@ import MapboxDirections import UIKit /// :nodoc: -@objc(MBLaneView) open class LaneView: UIView { @IBInspectable var scale: CGFloat = 1 let invalidAlpha: CGFloat = 0.4 - var lane: Lane? + var lane: LaneIndication? var maneuverDirection: ManeuverDirection? var isValid: Bool = false @@ -16,13 +15,13 @@ open class LaneView: UIView { bounds.size } - @objc public dynamic var primaryColor: UIColor = .defaultLaneArrowPrimary { + public dynamic var primaryColor: UIColor = .defaultLaneArrowPrimary { didSet { setNeedsDisplay() } } - @objc public dynamic var secondaryColor: UIColor = .defaultLaneArrowSecondary { + public dynamic var secondaryColor: UIColor = .defaultLaneArrowSecondary { didSet { setNeedsDisplay() } diff --git a/MapboxNavigation/LanesView.swift b/MapboxNavigation/LanesView.swift index 38fb2b5a..d5ca5fa4 100644 --- a/MapboxNavigation/LanesView.swift +++ b/MapboxNavigation/LanesView.swift @@ -69,7 +69,6 @@ open class LanesView: UIView { /** Updates the tertiary instructions banner info with a given `VisualInstructionBanner`. */ - @objc(updateForVisualInstructionBanner:) public func update(for visualInstruction: VisualInstructionBanner?) { self.clearLaneViews() @@ -79,9 +78,16 @@ open class LanesView: UIView { return } - let laneIndications: [LaneIndicationComponent]? = tertiaryInstruction.components.compactMap { $0 as? LaneIndicationComponent } - - guard let lanes = laneIndications, !lanes.isEmpty else { + let lanes = tertiaryInstruction.components.compactMap { + switch $0 { + case let .lane(indications, isUsable, preferredDirection): + self + default: + nil + } + } + + guard !lanes.isEmpty else { self.hide() return } diff --git a/MapboxNavigation/NavigationMapView.swift b/MapboxNavigation/NavigationMapView.swift index 11355902..dc424202 100644 --- a/MapboxNavigation/NavigationMapView.swift +++ b/MapboxNavigation/NavigationMapView.swift @@ -22,22 +22,22 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { /** Returns the altitude that the map camera initally defaults to. */ - @objc public var defaultAltitude: CLLocationDistance = 1000.0 + public var defaultAltitude: CLLocationDistance = 1000.0 /** Returns the altitude the map conditionally zooms out to when user is on a motorway, and the maneuver length is sufficently long. */ - @objc public var zoomedOutMotorwayAltitude: CLLocationDistance = 2000.0 + public var zoomedOutMotorwayAltitude: CLLocationDistance = 2000.0 /** Returns the threshold for what the map considers a "long-enough" maneuver distance to trigger a zoom-out when the user enters a motorway. */ - @objc public var longManeuverDistance: CLLocationDistance = 1000.0 + public var longManeuverDistance: CLLocationDistance = 1000.0 /** Maximum distance the user can tap for a selection to be valid when selecting an alternate route. */ - @objc public var tapGestureDistanceThreshold: CGFloat = 50 + public var tapGestureDistanceThreshold: CGFloat = 50 /** The object that acts as the navigation delegate of the map view. @@ -71,17 +71,17 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { let instructionLabel = "instructionLabel" let instructionCircle = "instructionCircle" - @objc public dynamic var trafficUnknownColor: UIColor = .trafficUnknown - @objc public dynamic var trafficLowColor: UIColor = .trafficLow - @objc public dynamic var trafficModerateColor: UIColor = .trafficModerate - @objc public dynamic var trafficHeavyColor: UIColor = .trafficHeavy - @objc public dynamic var trafficSevereColor: UIColor = .trafficSevere - @objc public dynamic var routeLineColor: UIColor = .defaultRouteLine - @objc public dynamic var routeLineAlternativeColor: UIColor = .defaultRouteLineAlternative - @objc public dynamic var routeLineCasingColor: UIColor = .defaultRouteLineCasing - @objc public dynamic var routeLineCasingAlternativeColor: UIColor = .defaultRouteLineCasingAlternative - @objc public dynamic var maneuverArrowColor: UIColor = .defaultManeuverArrow - @objc public dynamic var maneuverArrowStrokeColor: UIColor = .defaultManeuverArrowStroke + public dynamic var trafficUnknownColor: UIColor = .trafficUnknown + public dynamic var trafficLowColor: UIColor = .trafficLow + public dynamic var trafficModerateColor: UIColor = .trafficModerate + public dynamic var trafficHeavyColor: UIColor = .trafficHeavy + public dynamic var trafficSevereColor: UIColor = .trafficSevere + public dynamic var routeLineColor: UIColor = .defaultRouteLine + public dynamic var routeLineAlternativeColor: UIColor = .defaultRouteLineAlternative + public dynamic var routeLineCasingColor: UIColor = .defaultRouteLineCasing + public dynamic var routeLineCasingAlternativeColor: UIColor = .defaultRouteLineCasingAlternative + public dynamic var maneuverArrowColor: UIColor = .defaultManeuverArrow + public dynamic var maneuverArrowStrokeColor: UIColor = .defaultManeuverArrowStroke var userLocationForCourseTracking: CLLocation? var animatesUserLocation: Bool = false @@ -128,7 +128,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { - seealso: NavigationMapViewDelegate.navigationMapViewUserAnchorPoint(_:) */ var userAnchorPoint: CGPoint { - if let anchorPoint = navigationMapDelegate?.navigationMapViewUserAnchorPoint?(self), anchorPoint != .zero { + if let anchorPoint = navigationMapDelegate?.navigationMapViewUserAnchorPoint(self), anchorPoint != .zero { return anchorPoint } @@ -158,9 +158,9 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { self.enableFrameByFrameCourseViewTracking(for: 3) self.altitude = self.defaultAltitude self.showsUserLocation = true - self.courseTrackingDelegate?.navigationMapViewDidStartTrackingCourse?(self) + self.courseTrackingDelegate?.navigationMapViewDidStartTrackingCourse(self) } else { - self.courseTrackingDelegate?.navigationMapViewDidStopTrackingCourse?(self) + self.courseTrackingDelegate?.navigationMapViewDidStopTrackingCourse(self) } if let location = userLocationForCourseTracking { self.updateCourseTracking(location: location, animated: true) @@ -173,7 +173,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { If the view conforms to `UserCourseView`, its `UserCourseView.update(location:pitch:direction:animated:)` method is frequently called to ensure that its visual appearance matches the map’s camera. */ - @objc public var userCourseView: UIView? { + public var userCourseView: UIView? { didSet { oldValue?.removeFromSuperview() if let userCourseView { @@ -288,7 +288,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { let routeProgress = notification.userInfo![RouteControllerNotificationUserInfoKey.routeProgressKey] as! RouteProgress if let location = userLocationForCourseTracking { - let cameraUpdated = self.courseTrackingDelegate?.updateCamera?(self, location: location, routeProgress: routeProgress) ?? false + let cameraUpdated = self.courseTrackingDelegate?.updateCamera(self, location: location, routeProgress: routeProgress) ?? false if !cameraUpdated { let newCamera = MLNMapCamera(lookingAtCenter: location.coordinate, acrossDistance: self.altitude, pitch: 45, heading: location.course) @@ -323,7 +323,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { Track position on a frame by frame basis. Used for first location update and when resuming tracking mode. Call this method when you are doing custom zoom animations, this will make sure the puck stays on the route during these animations. */ - @objc public func enableFrameByFrameCourseViewTracking(for duration: TimeInterval) { + public func enableFrameByFrameCourseViewTracking(for duration: TimeInterval) { NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.disableFrameByFramePositioning), object: nil) perform(#selector(self.disableFrameByFramePositioning), with: nil, afterDelay: duration) self.shouldPositionCourseViewFrameByFrame = true @@ -338,7 +338,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { self.tracksUserCourse = false } - @objc public func updateCourseTracking(location: CLLocation?, camera: MLNMapCamera? = nil, animated: Bool = false) { + public func updateCourseTracking(location: CLLocation?, camera: MLNMapCamera? = nil, animated: Bool = false) { // While animating to overhead mode, don't animate the puck. let duration: TimeInterval = animated && !self.isAnimatingToOverheadMode ? 1 : 0 self.animatesUserLocation = animated @@ -374,11 +374,11 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { let waypointTest = self.waypoints(on: routes, closeTo: tapPoint) // are there waypoints near the tapped location? if let selected = waypointTest?.first { // test passes - self.navigationMapDelegate?.navigationMapView?(self, didSelect: selected) + self.navigationMapDelegate?.navigationMapView(self, didSelect: selected) return } else if let routes = self.routes(closeTo: tapPoint) { guard let selectedRoute = routes.first else { return } - self.navigationMapDelegate?.navigationMapView?(self, didSelect: selectedRoute) + self.navigationMapDelegate?.navigationMapView(self, didSelect: selectedRoute) } } @@ -421,9 +421,9 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { */ public static let defaultPadding: UIEdgeInsets = .init(top: 10, left: 20, bottom: 10, right: 20) - @objc public func showcase(_ routes: [Route], padding: UIEdgeInsets = NavigationMapView.defaultPadding, animated: Bool = false) { + public func showcase(_ routes: [Route], padding: UIEdgeInsets = NavigationMapView.defaultPadding, animated: Bool = false) { guard let active = routes.first, - let coords = active.coordinates, + let coords = active.shape?.coordinates, !coords.isEmpty else { return } // empty array self.removeArrow() @@ -437,7 +437,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { } func fit(to route: Route, facing direction: CLLocationDirection = 0, padding: UIEdgeInsets = NavigationMapView.defaultPadding, animated: Bool = false) { - guard let coords = route.coordinates, !coords.isEmpty else { return } + guard let coords = route.shape?.coordinates, !coords.isEmpty else { return } setUserTrackingMode(.none, animated: false, completionHandler: nil) let line = MLNPolyline(coordinates: coords, count: UInt(coords.count)) @@ -449,13 +449,13 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { /** Adds or updates both the route line and the route line casing */ - @objc public func showRoutes(_ routes: [Route], legIndex: Int = 0) { + public func showRoutes(_ routes: [Route], legIndex: Int = 0) { guard let style else { return } guard let mainRoute = routes.first else { return } self.routes = routes - let polylines = self.navigationMapDelegate?.navigationMapView?(self, shapeFor: routes) ?? self.shape(for: routes, legIndex: legIndex) - let mainPolylineSimplified = self.navigationMapDelegate?.navigationMapView?(self, simplifiedShapeFor: mainRoute) ?? self.shape(forCasingOf: mainRoute, legIndex: legIndex) + let polylines = self.navigationMapDelegate?.navigationMapView(self, shapeFor: routes) ?? self.shape(for: routes, legIndex: legIndex) + let mainPolylineSimplified = self.navigationMapDelegate?.navigationMapView(self, simplifiedShapeFor: mainRoute) ?? self.shape(forCasingOf: mainRoute, legIndex: legIndex) if let source = style.source(withIdentifier: sourceIdentifier) as? MLNShapeSource, let sourceSimplified = style.source(withIdentifier: sourceCasingIdentifier) as? MLNShapeSource { @@ -467,8 +467,8 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { style.addSource(lineSource) style.addSource(lineCasingSource) - let line = self.navigationMapDelegate?.navigationMapView?(self, routeStyleLayerWithIdentifier: self.routeLayerIdentifier, source: lineSource) ?? self.routeStyleLayer(identifier: self.routeLayerIdentifier, source: lineSource) - let lineCasing = self.navigationMapDelegate?.navigationMapView?(self, routeCasingStyleLayerWithIdentifier: self.routeLayerCasingIdentifier, source: lineCasingSource) ?? self.routeCasingStyleLayer(identifier: self.routeLayerCasingIdentifier, source: lineSource) + let line = self.navigationMapDelegate?.navigationMapView(self, routeStyleLayerWithIdentifier: self.routeLayerIdentifier, source: lineSource) ?? self.routeStyleLayer(identifier: self.routeLayerIdentifier, source: lineSource) + let lineCasing = self.navigationMapDelegate?.navigationMapView(self, routeCasingStyleLayerWithIdentifier: self.routeLayerCasingIdentifier, source: lineCasingSource) ?? self.routeCasingStyleLayer(identifier: self.routeLayerCasingIdentifier, source: lineSource) for layer in style.layers.reversed() { if !(layer is MLNSymbolStyleLayer), @@ -484,7 +484,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { /** Removes route line and route line casing from map */ - @objc public func removeRoutes() { + public func removeRoutes() { guard let style else { return } @@ -509,15 +509,15 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { /** Adds the route waypoints to the map given the current leg index. Previous waypoints for completed legs will be omitted. */ - @objc public func showWaypoints(_ route: Route, legIndex: Int = 0) { + public func showWaypoints(_ route: Route, legIndex: Int = 0) { guard let style else { return } - let waypoints: [Waypoint] = Array(route.legs.map(\.destination).dropLast()) + let waypoints: [Waypoint] = Array(route.legs.compactMap(\.destination).dropLast()) - let source = self.navigationMapDelegate?.navigationMapView?(self, shapeFor: waypoints, legIndex: legIndex) ?? self.shape(for: waypoints, legIndex: legIndex) - if route.routeOptions.waypoints.count > 2 { // are we on a multipoint route? + let source = self.navigationMapDelegate?.navigationMapView(self, shapeFor: waypoints, legIndex: legIndex) ?? self.shape(for: waypoints, legIndex: legIndex) + if waypoints.count > 2 { // are we on a multipoint route? self.routes = [route] // update the model if let waypointSource = style.source(withIdentifier: waypointSourceIdentifier) as? MLNShapeSource { waypointSource.shape = source @@ -525,8 +525,8 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { let sourceShape = MLNShapeSource(identifier: waypointSourceIdentifier, shape: source, options: sourceOptions) style.addSource(sourceShape) - let circles = self.navigationMapDelegate?.navigationMapView?(self, waypointStyleLayerWithIdentifier: self.waypointCircleIdentifier, source: sourceShape) ?? self.routeWaypointCircleStyleLayer(identifier: self.waypointCircleIdentifier, source: sourceShape) - let symbols = self.navigationMapDelegate?.navigationMapView?(self, waypointSymbolStyleLayerWithIdentifier: self.waypointSymbolIdentifier, source: sourceShape) ?? self.routeWaypointSymbolStyleLayer(identifier: self.waypointSymbolIdentifier, source: sourceShape) + let circles = self.navigationMapDelegate?.navigationMapView(self, waypointStyleLayerWithIdentifier: self.waypointCircleIdentifier, source: sourceShape) ?? self.routeWaypointCircleStyleLayer(identifier: self.waypointCircleIdentifier, source: sourceShape) + let symbols = self.navigationMapDelegate?.navigationMapView(self, waypointSymbolStyleLayerWithIdentifier: self.waypointSymbolIdentifier, source: sourceShape) ?? self.routeWaypointSymbolStyleLayer(identifier: self.waypointSymbolIdentifier, source: sourceShape) if let arrowLayer = style.layer(withIdentifier: arrowCasingSymbolLayerIdentifier) { style.insertLayer(circles, below: arrowLayer) @@ -541,7 +541,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { if let lastLeg = route.legs.last { removeAnnotations(annotations ?? []) let destination = MLNPointAnnotation() - destination.coordinate = lastLeg.destination.coordinate + destination.coordinate = lastLeg.destination!.coordinate addAnnotation(destination) } } @@ -549,7 +549,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { /** Removes all waypoints from the map. */ - @objc public func removeWaypoints() { + public func removeWaypoints() { guard let style else { return } removeAnnotations(annotations ?? []) @@ -574,13 +574,13 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { /** Shows the step arrow given the current `RouteProgress`. */ - @objc public func addArrow(route: Route, legIndex: Int, stepIndex: Int) { + public func addArrow(route: Route, legIndex: Int, stepIndex: Int) { guard route.legs.indices.contains(legIndex), route.legs[legIndex].steps.indices.contains(stepIndex) else { return } let step = route.legs[legIndex].steps[stepIndex] let maneuverCoordinate = step.maneuverLocation - guard let routeCoordinates = route.coordinates else { return } + guard let routeCoordinates = route.shape?.coordinates else { return } guard let style else { return @@ -595,9 +595,11 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { let minimumZoomLevel: Float = 14.5 let shaftLength = max(min(30 * metersPerPoint(atLatitude: maneuverCoordinate.latitude), 30), 10) - let polyline = Polyline(routeCoordinates) - let shaftCoordinates = Array(polyline.trimmed(from: maneuverCoordinate, distance: -shaftLength).coordinates.reversed() - + polyline.trimmed(from: maneuverCoordinate, distance: shaftLength).coordinates.suffix(from: 1)) + let polyline = LineString(routeCoordinates) + guard let trimmedPolylineBegin = polyline.trimmed(from: maneuverCoordinate, distance: -shaftLength) else { return } + guard let trimmedPolylineEnd = polyline.trimmed(from: maneuverCoordinate, distance: shaftLength) else { return } + + let shaftCoordinates = Array(trimmedPolylineBegin.coordinates.reversed() + trimmedPolylineEnd.coordinates.suffix(from: 1)) if shaftCoordinates.count > 1 { var shaftStrokeCoordinates = shaftCoordinates let shaftStrokePolyline = ArrowStrokePolyline(coordinates: &shaftStrokeCoordinates, count: UInt(shaftStrokeCoordinates.count)) @@ -693,7 +695,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { /** Removes the step arrow from the map. */ - @objc public func removeArrow() { + public func removeArrow() { guard let style else { return } @@ -746,9 +748,9 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { // TODO: Change to point-based distance calculation private func waypoints(on routes: [Route], closeTo point: CGPoint) -> [Waypoint]? { let tapCoordinate = convert(point, toCoordinateFrom: self) - let multipointRoutes = routes.filter { $0.routeOptions.waypoints.count >= 3 } + let multipointRoutes = routes.filter { $0.legSeparators.count >= 3 } guard multipointRoutes.count > 0 else { return nil } - let waypoints = multipointRoutes.flatMap(\.routeOptions.waypoints) + let waypoints = multipointRoutes.flatMap(\.legSeparators).compactMap { $0 } // lets sort the array in order of closest to tap let closest = waypoints.sorted { left, right -> Bool in @@ -770,14 +772,14 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { let tapCoordinate = convert(point, toCoordinateFrom: self) // do we have routes? If so, filter routes with at least 2 coordinates. - guard let routes = routes?.filter({ $0.coordinates?.count ?? 0 > 1 }) else { return nil } + guard let routes = self.routes?.filter({ $0.shape?.coordinates.count ?? 0 > 1 }) else { return nil } // Sort routes by closest distance to tap gesture. let closest = routes.sorted { left, right -> Bool in // existance has been assured through use of filter. - let leftLine = Polyline(left.coordinates!) - let rightLine = Polyline(right.coordinates!) + let leftLine = LineString(left.shape!.coordinates) + let rightLine = LineString(right.shape!.coordinates) let leftDistance = leftLine.closestCoordinate(to: tapCoordinate)!.distance let rightDistance = rightLine.closestCoordinate(to: tapCoordinate)!.distance @@ -786,7 +788,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { // filter closest coordinates by which ones are under threshold. let candidates = closest.filter { - let closestCoordinate = Polyline($0.coordinates!).closestCoordinate(to: tapCoordinate)!.coordinate + let closestCoordinate = LineString($0.shape!.coordinates).closestCoordinate(to: tapCoordinate)!.coordinate let closestPoint = self.convert(closestCoordinate, toPointTo: self) return closestPoint.distance(to: point) < self.tapGestureDistanceThreshold @@ -801,7 +803,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { var altRoutes: [MLNPolylineFeature] = [] for route in routes.suffix(from: 1) { - let polyline = MLNPolylineFeature(coordinates: route.coordinates!, count: UInt(route.coordinates!.count)) + let polyline = MLNPolylineFeature(coordinates: route.shape!.coordinates, count: UInt(route.shape!.coordinates.count)) polyline.attributes["isAlternateRoute"] = true altRoutes.append(polyline) } @@ -810,21 +812,21 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { } func addCongestion(to route: Route, legIndex: Int?) -> [MLNPolylineFeature]? { - guard let coordinates = route.coordinates else { return nil } + guard let coordinates = route.shape?.coordinates else { return nil } var linesPerLeg: [MLNPolylineFeature] = [] for (index, leg) in route.legs.enumerated() { // If there is no congestion, don't try and add it guard let legCongestion = leg.segmentCongestionLevels, legCongestion.count < coordinates.count else { - return [MLNPolylineFeature(coordinates: route.coordinates!, count: UInt(route.coordinates!.count))] + return [MLNPolylineFeature(coordinates: route.shape!.coordinates, count: UInt(route.shape!.coordinates.count))] } // The last coord of the preceding step, is shared with the first coord of the next step, we don't need both. let legCoordinates: [CLLocationCoordinate2D] = leg.steps.enumerated().reduce([]) { allCoordinates, current in let index = current.offset let step = current.element - let stepCoordinates = step.coordinates! + let stepCoordinates = step.shape!.coordinates return index == 0 ? stepCoordinates : allCoordinates + stepCoordinates.suffix(from: 1) } @@ -870,7 +872,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { var linesPerLeg: [MLNPolylineFeature] = [] for (index, leg) in route.legs.enumerated() { - let legCoordinates: [CLLocationCoordinate2D] = Array(leg.steps.compactMap(\.coordinates).joined()) + let legCoordinates: [CLLocationCoordinate2D] = leg.steps.flatMap { $0.shape!.coordinates } let polyline = MLNPolylineFeature(coordinates: legCoordinates, count: UInt(legCoordinates.count)) if let legIndex { @@ -993,7 +995,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { `NavigationViewController` is localized automatically, so you do not need to call this method on the value of `NavigationViewController.mapView`. */ - @objc public func localizeLabels() { + public func localizeLabels() { guard let style else { return } @@ -1022,7 +1024,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { } } - @objc public func showVoiceInstructionsOnMap(route: Route) { + public func showVoiceInstructionsOnMap(route: Route) { guard let style else { return } @@ -1032,7 +1034,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { for (stepIndex, step) in leg.steps.enumerated() { for instruction in step.instructionsSpokenAlongStep! { let feature = MLNPointFeature() - feature.coordinate = Polyline(route.legs[legIndex].steps[stepIndex].coordinates!.reversed()).coordinateFromStart(distance: instruction.distanceAlongStep)! + feature.coordinate = LineString(route.legs[legIndex].steps[stepIndex].shape!.coordinates.reversed()).coordinateFromStart(distance: instruction.distanceAlongStep)! feature.attributes = ["instruction": instruction.text] features.append(feature) } @@ -1069,9 +1071,9 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { /** Sets the camera directly over a series of coordinates. */ - @objc public func setOverheadCameraView(from userLocation: CLLocationCoordinate2D, along coordinates: [CLLocationCoordinate2D], for bounds: UIEdgeInsets) { + public func setOverheadCameraView(from userLocation: CLLocationCoordinate2D, along coordinates: [CLLocationCoordinate2D], for bounds: UIEdgeInsets) { self.isAnimatingToOverheadMode = true - let slicedLine = Polyline(coordinates).sliced(from: userLocation).coordinates + guard let slicedLine = LineString(coordinates).sliced(from: userLocation)?.coordinates else { return } let line = MLNPolyline(coordinates: slicedLine, count: UInt(slicedLine.count)) self.tracksUserCourse = false @@ -1103,7 +1105,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { /** Recenters the camera and begins tracking the user's location. */ - @objc public func recenterMap() { + public func recenterMap() { self.tracksUserCourse = true self.enableFrameByFrameCourseViewTracking(for: 3) } @@ -1112,7 +1114,6 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { /** The `NavigationMapViewDelegate` provides methods for configuring the NavigationMapView, as well as responding to events triggered by the NavigationMapView. */ -@objc(MBNavigationMapViewDelegate) public protocol NavigationMapViewDelegate: AnyObject { /** Asks the receiver to return an MLNStyleLayer for routes, given an identifier and source. @@ -1122,7 +1123,7 @@ public protocol NavigationMapViewDelegate: AnyObject { - parameter source: The Layer source containing the route data that this method would style. - returns: An MLNStyleLayer that the map applies to all routes. */ - @objc optional func navigationMapView(_ mapView: NavigationMapView, routeStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? + func navigationMapView(_ mapView: NavigationMapView, routeStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? /** Asks the receiver to return an MLNStyleLayer for waypoints, given an identifier and source. @@ -1132,7 +1133,7 @@ public protocol NavigationMapViewDelegate: AnyObject { - parameter source: The Layer source containing the waypoint data that this method would style. - returns: An MLNStyleLayer that the map applies to all waypoints. */ - @objc optional func navigationMapView(_ mapView: NavigationMapView, waypointStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? + func navigationMapView(_ mapView: NavigationMapView, waypointStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? /** Asks the receiver to return an MLNStyleLayer for waypoint symbols, given an identifier and source. @@ -1142,7 +1143,7 @@ public protocol NavigationMapViewDelegate: AnyObject { - parameter source: The Layer source containing the waypoint data that this method would style. - returns: An MLNStyleLayer that the map applies to all waypoint symbols. */ - @objc optional func navigationMapView(_ mapView: NavigationMapView, waypointSymbolStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? + func navigationMapView(_ mapView: NavigationMapView, waypointSymbolStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? /** Asks the receiver to return an MLNStyleLayer for route casings, given an identifier and source. @@ -1153,23 +1154,21 @@ public protocol NavigationMapViewDelegate: AnyObject { - parameter source: The Layer source containing the route data that this method would style. - returns: An MLNStyleLayer that the map applies to the route. */ - @objc optional func navigationMapView(_ mapView: NavigationMapView, routeCasingStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? + func navigationMapView(_ mapView: NavigationMapView, routeCasingStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? /** Tells the receiver that the user has selected a route by interacting with the map view. - parameter mapView: The NavigationMapView. - parameter route: The route that was selected. */ - @objc(navigationMapView:didSelectRoute:) - optional func navigationMapView(_ mapView: NavigationMapView, didSelect route: Route) + func navigationMapView(_ mapView: NavigationMapView, didSelect route: Route) /** Tells the receiver that a waypoint was selected. - parameter mapView: The NavigationMapView. - parameter waypoint: The waypoint that was selected. */ - @objc(navigationMapView:didSelectWaypoint:) - optional func navigationMapView(_ mapView: NavigationMapView, didSelect waypoint: Waypoint) + func navigationMapView(_ mapView: NavigationMapView, didSelect waypoint: Waypoint) /** Asks the receiver to return an MLNShape that describes the geometry of the route. @@ -1178,8 +1177,7 @@ public protocol NavigationMapViewDelegate: AnyObject { - parameter routes: The routes that the sender is asking about. The first route will always be rendered as the main route, while all subsequent routes will be rendered as alternate routes. - returns: Optionally, a `MLNShape` that defines the shape of the route, or `nil` to use default behavior. */ - @objc(navigationMapView:shapeForRoutes:) - optional func navigationMapView(_ mapView: NavigationMapView, shapeFor routes: [Route]) -> MLNShape? + func navigationMapView(_ mapView: NavigationMapView, shapeFor routes: [Route]) -> MLNShape? /** Asks the receiver to return an MLNShape that describes the geometry of the route at lower zoomlevels. @@ -1188,8 +1186,7 @@ public protocol NavigationMapViewDelegate: AnyObject { - parameter route: The route that the sender is asking about. - returns: Optionally, a `MLNShape` that defines the shape of the route at lower zoomlevels, or `nil` to use default behavior. */ - @objc(navigationMapView:simplifiedShapeForRoute:) - optional func navigationMapView(_ mapView: NavigationMapView, simplifiedShapeFor route: Route) -> MLNShape? + func navigationMapView(_ mapView: NavigationMapView, simplifiedShapeFor route: Route) -> MLNShape? /** Asks the receiver to return an MLNShape that describes the geometry of the waypoint. @@ -1197,8 +1194,7 @@ public protocol NavigationMapViewDelegate: AnyObject { - parameter waypoints: The waypoints to be displayed on the map. - returns: Optionally, a `MLNShape` that defines the shape of the waypoint, or `nil` to use default behavior. */ - @objc(navigationMapView:shapeForWaypoints:legIndex:) - optional func navigationMapView(_ mapView: NavigationMapView, shapeFor waypoints: [Waypoint], legIndex: Int) -> MLNShape? + func navigationMapView(_ mapView: NavigationMapView, shapeFor waypoints: [Waypoint], legIndex: Int) -> MLNShape? /** Asks the receiver to return an MLNAnnotationImage that describes the image used an annotation. @@ -1206,8 +1202,7 @@ public protocol NavigationMapViewDelegate: AnyObject { - parameter annotation: The annotation to be styled. - returns: Optionally, a `MLNAnnotationImage` that defines the image used for the annotation. */ - @objc(navigationMapView:imageForAnnotation:) - optional func navigationMapView(_ mapView: MLNMapView, imageFor annotation: MLNAnnotation) -> MLNAnnotationImage? + func navigationMapView(_ mapView: MLNMapView, imageFor annotation: MLNAnnotation) -> MLNAnnotationImage? /** Asks the receiver to return an MLNAnnotationView that describes the image used an annotation. @@ -1215,8 +1210,7 @@ public protocol NavigationMapViewDelegate: AnyObject { - parameter annotation: The annotation to be styled. - returns: Optionally, a `MLNAnnotationView` that defines the view used for the annotation. */ - @objc(navigationMapView:viewForAnnotation:) - optional func navigationMapView(_ mapView: MLNMapView, viewFor annotation: MLNAnnotation) -> MLNAnnotationView? + func navigationMapView(_ mapView: MLNMapView, viewFor annotation: MLNAnnotation) -> MLNAnnotationView? /** Asks the receiver to return a CGPoint to serve as the anchor for the user icon. @@ -1224,8 +1218,7 @@ public protocol NavigationMapViewDelegate: AnyObject { - parameter mapView: The NavigationMapView. - returns: A CGPoint (in regular coordinate-space) that represents the point on-screen where the user location icon should be drawn. */ - @objc(navigationMapViewUserAnchorPoint:) - optional func navigationMapViewUserAnchorPoint(_ mapView: NavigationMapView) -> CGPoint + func navigationMapViewUserAnchorPoint(_ mapView: NavigationMapView) -> CGPoint } // MARK: NavigationMapViewCourseTrackingDelegate @@ -1233,23 +1226,20 @@ public protocol NavigationMapViewDelegate: AnyObject { /** The `NavigationMapViewCourseTrackingDelegate` provides methods for responding to the `NavigationMapView` starting or stopping course tracking. */ -@objc(MBNavigationMapViewCourseTrackingDelegate) public protocol NavigationMapViewCourseTrackingDelegate: AnyObject { /** Tells the receiver that the map is now tracking the user course. - seealso: NavigationMapView.tracksUserCourse - parameter mapView: The NavigationMapView. */ - @objc(navigationMapViewDidStartTrackingCourse:) - optional func navigationMapViewDidStartTrackingCourse(_ mapView: NavigationMapView) + func navigationMapViewDidStartTrackingCourse(_ mapView: NavigationMapView) /** Tells the receiver that `tracksUserCourse` was set to false, signifying that the map is no longer tracking the user course. - seealso: NavigationMapView.tracksUserCourse - parameter mapView: The NavigationMapView. */ - @objc(navigationMapViewDidStopTrackingCourse:) - optional func navigationMapViewDidStopTrackingCourse(_ mapView: NavigationMapView) + func navigationMapViewDidStopTrackingCourse(_ mapView: NavigationMapView) /** Allows to pass a custom camera location given the current route progress. If you return true, the camera won't be updated by the NavigationMapView. @@ -1257,6 +1247,5 @@ public protocol NavigationMapViewCourseTrackingDelegate: AnyObject { - parameter location: The current user location - parameter routeProgress: The current route progress */ - @objc(navigationMapView:location:routeProgress:) - optional func updateCamera(_ mapView: NavigationMapView, location: CLLocation, routeProgress: RouteProgress) -> Bool + func updateCamera(_ mapView: NavigationMapView, location: CLLocation, routeProgress: RouteProgress) -> Bool } diff --git a/MapboxNavigation/NavigationViewController.swift b/MapboxNavigation/NavigationViewController.swift index c370b661..183ca03e 100644 --- a/MapboxNavigation/NavigationViewController.swift +++ b/MapboxNavigation/NavigationViewController.swift @@ -9,14 +9,13 @@ import CarPlay /** The `NavigationViewControllerDelegate` protocol provides methods for configuring the map view shown by a `NavigationViewController` and responding to the cancellation of a navigation session. */ -@objc(MBNavigationViewControllerDelegate) public protocol NavigationViewControllerDelegate: VisualInstructionDelegate { /** Called when user arrived at the destination of the trip. - parameter navigationViewController: The navigation view controller that finished navigation. */ - @objc optional func navigationViewControllerDidFinishRouting(_ navigationViewController: NavigationViewController) + func navigationViewControllerDidFinishRouting(_ navigationViewController: NavigationViewController) /** Called when the underlaying mapView finished loading the style. @@ -24,7 +23,7 @@ public protocol NavigationViewControllerDelegate: VisualInstructionDelegate { - parameter navigationViewController: The navigation view controller that finished navigation. - parameter style: The applied style */ - @objc optional func navigationViewController(_ navigationViewController: NavigationViewController, didFinishLoading style: MLNStyle) + func navigationViewController(_ navigationViewController: NavigationViewController, didFinishLoading style: MLNStyle) /** Called when the user arrives at the destination waypoint for a route leg. @@ -36,8 +35,7 @@ public protocol NavigationViewControllerDelegate: VisualInstructionDelegate { - parameter waypoint: The waypoint that the user has arrived at. - returns: True to automatically advance to the next leg, or false to remain on the now completed leg. */ - @objc(navigationViewController:didArriveAtWaypoint:) - optional func navigationViewController(_ navigationViewController: NavigationViewController, didArriveAt waypoint: Waypoint) -> Bool + func navigationViewController(_ navigationViewController: NavigationViewController, didArriveAt waypoint: Waypoint) -> Bool /** Returns whether the navigation view controller should be allowed to calculate a new route. @@ -48,8 +46,7 @@ public protocol NavigationViewControllerDelegate: VisualInstructionDelegate { - parameter location: The user’s current location. - returns: True to allow the navigation view controller to calculate a new route; false to keep tracking the current route. */ - @objc(navigationViewController:shouldRerouteFromLocation:) - optional func navigationViewController(_ navigationViewController: NavigationViewController, shouldRerouteFrom location: CLLocation) -> Bool + func navigationViewController(_ navigationViewController: NavigationViewController, shouldRerouteFrom location: CLLocation) -> Bool /** Called immediately before the navigation view controller calculates a new route. @@ -59,8 +56,7 @@ public protocol NavigationViewControllerDelegate: VisualInstructionDelegate { - parameter navigationViewController: The navigation view controller that will calculate a new route. - parameter location: The user’s current location. */ - @objc(navigationViewController:willRerouteFromLocation:) - optional func navigationViewController(_ navigationViewController: NavigationViewController, willRerouteFrom location: CLLocation) + func navigationViewController(_ navigationViewController: NavigationViewController, willRerouteFrom location: CLLocation) /** Called immediately after the navigation view controller receives a new route. @@ -70,8 +66,7 @@ public protocol NavigationViewControllerDelegate: VisualInstructionDelegate { - parameter navigationViewController: The navigation view controller that has calculated a new route. - parameter route: The new route. */ - @objc(navigationViewController:didRerouteAlongRoute:) - optional func navigationViewController(_ navigationViewController: NavigationViewController, didRerouteAlong route: Route) + func navigationViewController(_ navigationViewController: NavigationViewController, didRerouteAlong route: Route) /** Called when the navigation view controller fails to receive a new route. @@ -81,96 +76,89 @@ public protocol NavigationViewControllerDelegate: VisualInstructionDelegate { - parameter navigationViewController: The navigation view controller that has calculated a new route. - parameter error: An error raised during the process of obtaining a new route. */ - @objc(navigationViewController:didFailToRerouteWithError:) - optional func navigationViewController(_ navigationViewController: NavigationViewController, didFailToRerouteWith error: Error) + func navigationViewController(_ navigationViewController: NavigationViewController, didFailToRerouteWith error: Error) /** Returns an `MLNStyleLayer` that determines the appearance of the route line. If this method is unimplemented, the navigation view controller’s map view draws the route line using an `MLNLineStyleLayer`. */ - @objc optional func navigationViewController(_ navigationViewController: NavigationViewController, routeStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? + func navigationViewController(_ navigationViewController: NavigationViewController, routeStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? /** Returns an `MLNStyleLayer` that determines the appearance of the route line’s casing. If this method is unimplemented, the navigation view controller’s map view draws the route line’s casing using an `MLNLineStyleLayer` whose width is greater than that of the style layer returned by `navigationViewController(_:routeStyleLayerWithIdentifier:source:)`. */ - @objc optional func navigationViewController(_ navigationViewController: NavigationViewController, routeCasingStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? + func navigationViewController(_ navigationViewController: NavigationViewController, routeCasingStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? /** Returns an `MLNShape` that represents the path of the route line. If this method is unimplemented, the navigation view controller’s map view represents the route line using an `MLNPolylineFeature` based on `route`’s `coordinates` property. */ - @objc(navigationViewController:shapeForRoutes:) - optional func navigationViewController(_ navigationViewController: NavigationViewController, shapeFor routes: [Route]) -> MLNShape? + func navigationViewController(_ navigationViewController: NavigationViewController, shapeFor routes: [Route]) -> MLNShape? /** Returns an `MLNShape` that represents the path of the route line’s casing. If this method is unimplemented, the navigation view controller’s map view represents the route line’s casing using an `MLNPolylineFeature` identical to the one returned by `navigationViewController(_:shapeFor:)`. */ - @objc(navigationViewController:simplifiedShapeForRoute:) - optional func navigationViewController(_ navigationViewController: NavigationViewController, simplifiedShapeFor route: Route) -> MLNShape? + func navigationViewController(_ navigationViewController: NavigationViewController, simplifiedShapeFor route: Route) -> MLNShape? /* Returns an `MLNStyleLayer` that marks the location of each destination along the route when there are multiple destinations. The returned layer is added to the map below the layer returned by `navigationViewController(_:waypointSymbolStyleLayerWithIdentifier:source:)`. If this method is unimplemented, the navigation view controller’s map view marks each destination waypoint with a circle. */ - @objc optional func navigationViewController(_ navigationViewController: NavigationViewController, waypointStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? + func navigationViewController(_ navigationViewController: NavigationViewController, waypointStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? /* Returns an `MLNStyleLayer` that places an identifying symbol on each destination along the route when there are multiple destinations. The returned layer is added to the map above the layer returned by `navigationViewController(_:waypointStyleLayerWithIdentifier:source:)`. If this method is unimplemented, the navigation view controller’s map view labels each destination waypoint with a number, starting with 1 at the first destination, 2 at the second destination, and so on. */ - @objc optional func navigationViewController(_ navigationViewController: NavigationViewController, waypointSymbolStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? + func navigationViewController(_ navigationViewController: NavigationViewController, waypointSymbolStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? /** Returns an `MLNShape` that represents the destination waypoints along the route (that is, excluding the origin). If this method is unimplemented, the navigation map view represents the route waypoints using `navigationViewController(_:shapeFor:legIndex:)`. */ - @objc(navigationViewController:shapeForWaypoints:legIndex:) - optional func navigationViewController(_ navigationViewController: NavigationViewController, shapeFor waypoints: [Waypoint], legIndex: Int) -> MLNShape? + func navigationViewController(_ navigationViewController: NavigationViewController, shapeFor waypoints: [Waypoint], legIndex: Int) -> MLNShape? /** Called when the user taps to select a route on the navigation view controller’s map view. - parameter navigationViewController: The navigation view controller presenting the route that the user selected. - parameter route: The route on the map that the user selected. */ - @objc(navigationViewController:didSelectRoute:) - optional func navigationViewController(_ navigationViewController: NavigationViewController, didSelect route: Route) + func navigationViewController(_ navigationViewController: NavigationViewController, didSelect route: Route) /** Called when the user taps to select an annotation on the navigation view controller’s map view. - parameter navigationViewController: The navigation view controller presenting the route that the user selected. - parameter annotation: The annotation on the map that the user selected. */ - @objc optional func navigationViewController(_ navigationViewController: NavigationViewController, didSelect annotation: MLNAnnotation) + func navigationViewController(_ navigationViewController: NavigationViewController, didSelect annotation: MLNAnnotation) /** Return an `MLNAnnotationImage` that represents the destination marker. If this method is unimplemented, the navigation view controller’s map view will represent the destination annotation with the default marker. */ - @objc(navigationViewController:imageForAnnotation:) - optional func navigationViewController(_ navigationViewController: NavigationViewController, imageFor annotation: MLNAnnotation) -> MLNAnnotationImage? + func navigationViewController(_ navigationViewController: NavigationViewController, imageFor annotation: MLNAnnotation) -> MLNAnnotationImage? /** Returns a view object to mark the given point annotation object on the map. The user location annotation view can also be customized via this method. When annotation is an instance of `MLNUserLocation`, return an instance of `MLNUserLocationAnnotationView` (or a subclass thereof). Note that when `NavigationMapView.tracksUserCourse` is set to `true`, the navigation view controller’s map view uses a distinct user course view; to customize it, set the `NavigationMapView.userCourseView` property of the map view stored by the `NavigationViewController.mapView` property. */ - @objc(navigationViewController:viewForAnnotation:) - optional func navigationViewController(_ navigationViewController: NavigationViewController, viewFor annotation: MLNAnnotation) -> MLNAnnotationView? + func navigationViewController(_ navigationViewController: NavigationViewController, viewFor annotation: MLNAnnotation) -> MLNAnnotationView? /** Returns the center point of the user course view in screen coordinates relative to the map view. */ - @objc optional func navigationViewController(_ navigationViewController: NavigationViewController, mapViewUserAnchorPoint mapView: NavigationMapView) -> CGPoint + func navigationViewController(_ navigationViewController: NavigationViewController, mapViewUserAnchorPoint mapView: NavigationMapView) -> CGPoint /** Allows the delegate to decide whether to ignore a location update. @@ -181,8 +169,7 @@ public protocol NavigationViewControllerDelegate: VisualInstructionDelegate { - parameter location: The location that will be discarded. - returns: If `true`, the location is discarded and the `NavigationViewController` will not consider it. If `false`, the location will not be thrown out. */ - @objc(navigationViewController:shouldDiscardLocation:) - optional func navigationViewController(_ navigationViewController: NavigationViewController, shouldDiscard location: CLLocation) -> Bool + func navigationViewController(_ navigationViewController: NavigationViewController, shouldDiscard location: CLLocation) -> Bool /** Called to allow the delegate to customize the contents of the road name label that is displayed towards the bottom of the map view. @@ -193,8 +180,7 @@ public protocol NavigationViewControllerDelegate: VisualInstructionDelegate { - parameter location: The user’s current location. - returns: The road name to display in the label, or nil to hide the label. */ - @objc(navigationViewController:roadNameAtLocation:) - optional func navigationViewController(_ navigationViewController: NavigationViewController, roadNameAt location: CLLocation) -> String? + func navigationViewController(_ navigationViewController: NavigationViewController, roadNameAt location: CLLocation) -> String? } /** @@ -204,8 +190,6 @@ public protocol NavigationViewControllerDelegate: VisualInstructionDelegate { - seealso: CarPlayNavigationViewController */ -@objcMembers -@objc(MBNavigationViewController) open class NavigationViewController: UIViewController { private var locationManager: NavigationLocationManager @@ -240,7 +224,7 @@ open class NavigationViewController: UIViewController { } else { self.routeController?.routeProgress = RouteProgress(route: route) } - NavigationSettings.shared.distanceUnit = route.routeOptions.locale.usesMetric ? .kilometer : .mile + NavigationSettings.shared.distanceUnit = Locale.current.usesMetric ? .kilometer : .mile self.mapViewController.notifyDidReroute(route: route) } else { self.routeController = nil @@ -355,7 +339,6 @@ open class NavigationViewController: UIViewController { /// - nightStyleURL: URL for the style rules used to render the map during nighttime hours. If nil, `dayStyleURL` will be used at night as well. /// - directions: Used when recomputing a new route, for example if the user takes a wrong turn and needs re-routing. If unspecified, a default will be used. /// - voiceController: Produces voice instructions for route navigation. If nil, a default will be used. - @objc(initWithDayStyleURL:nightStyleURL:directions:voiceController:) public convenience init(dayStyleURL: URL, nightStyleURL: URL? = nil, directions: Directions = .shared, @@ -371,7 +354,6 @@ open class NavigationViewController: UIViewController { /// - nightStyle: Style used to render the map during nighttime hours. If nil, `dayStyle` will be used at night as well. /// - directions: Used when recomputing a new route, for example if the user takes a wrong turn and needs re-routing. If unspecified, a default will be used. /// - voiceController: Produces voice instructions for route navigation. If nil, a default will be used. - @objc(initWithDayStyle:nightStyle:directions:voiceController:) public required init(dayStyle: Style, nightStyle: Style? = nil, directions: Directions = Directions.shared, @@ -455,10 +437,6 @@ open class NavigationViewController: UIViewController { self.routeController?.resume() self.mapViewController.prepareForNavigation() - if !(route.routeOptions is NavigationRouteOptions) { - print("`Route` was created using `RouteOptions` and not `NavigationRouteOptions`. Although not required, this may lead to a suboptimal navigation experience. Without `NavigationRouteOptions`, it is not guaranteed you will get congestion along the route line, better ETAs and ETA label color dependent on congestion.") - } - if self.shouldManageApplicationIdleTimer { UIApplication.shared.isIdleTimerDisabled = true } @@ -536,61 +514,63 @@ open class NavigationViewController: UIViewController { // MARK: - RouteMapViewControllerDelegate extension NavigationViewController: RouteMapViewControllerDelegate { + public func navigationMapView(_ mapView: NavigationMapView, didSelect waypoint: MapboxDirections.Waypoint) {} + public func mapView(_ mapView: MLNMapView, didFinishLoading style: MLNStyle) { - self.delegate?.navigationViewController?(self, didFinishLoading: style) + self.delegate?.navigationViewController(self, didFinishLoading: style) } public func mapView(_ mapView: MLNMapView, didSelect annotation: any MLNAnnotation) { - self.delegate?.navigationViewController?(self, didSelect: annotation) + self.delegate?.navigationViewController(self, didSelect: annotation) } public func navigationMapView(_ mapView: NavigationMapView, routeCasingStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? { - self.delegate?.navigationViewController?(self, routeCasingStyleLayerWithIdentifier: identifier, source: source) + self.delegate?.navigationViewController(self, routeCasingStyleLayerWithIdentifier: identifier, source: source) } public func navigationMapView(_ mapView: NavigationMapView, routeStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? { - self.delegate?.navigationViewController?(self, routeStyleLayerWithIdentifier: identifier, source: source) + self.delegate?.navigationViewController(self, routeStyleLayerWithIdentifier: identifier, source: source) } public func navigationMapView(_ mapView: NavigationMapView, didSelect route: Route) { - self.delegate?.navigationViewController?(self, didSelect: route) + self.delegate?.navigationViewController(self, didSelect: route) } public func navigationMapView(_ mapView: NavigationMapView, shapeFor routes: [Route]) -> MLNShape? { - self.delegate?.navigationViewController?(self, shapeFor: routes) + self.delegate?.navigationViewController(self, shapeFor: routes) } public func navigationMapView(_ mapView: NavigationMapView, simplifiedShapeFor route: Route) -> MLNShape? { - self.delegate?.navigationViewController?(self, simplifiedShapeFor: route) + self.delegate?.navigationViewController(self, simplifiedShapeFor: route) } public func navigationMapView(_ mapView: NavigationMapView, waypointStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? { - self.delegate?.navigationViewController?(self, waypointStyleLayerWithIdentifier: identifier, source: source) + self.delegate?.navigationViewController(self, waypointStyleLayerWithIdentifier: identifier, source: source) } public func navigationMapView(_ mapView: NavigationMapView, waypointSymbolStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? { - self.delegate?.navigationViewController?(self, waypointSymbolStyleLayerWithIdentifier: identifier, source: source) + self.delegate?.navigationViewController(self, waypointSymbolStyleLayerWithIdentifier: identifier, source: source) } public func navigationMapView(_ mapView: NavigationMapView, shapeFor waypoints: [Waypoint], legIndex: Int) -> MLNShape? { - self.delegate?.navigationViewController?(self, shapeFor: waypoints, legIndex: legIndex) + self.delegate?.navigationViewController(self, shapeFor: waypoints, legIndex: legIndex) } public func navigationMapView(_ mapView: MLNMapView, imageFor annotation: MLNAnnotation) -> MLNAnnotationImage? { - self.delegate?.navigationViewController?(self, imageFor: annotation) + self.delegate?.navigationViewController(self, imageFor: annotation) } public func navigationMapView(_ mapView: MLNMapView, viewFor annotation: MLNAnnotation) -> MLNAnnotationView? { - self.delegate?.navigationViewController?(self, viewFor: annotation) + self.delegate?.navigationViewController(self, viewFor: annotation) } func mapViewControllerDidFinish(_ mapViewController: RouteMapViewController, byCanceling canceled: Bool) { self.endNavigation() - self.delegate?.navigationViewControllerDidFinishRouting?(self) + self.delegate?.navigationViewControllerDidFinishRouting(self) } public func navigationMapViewUserAnchorPoint(_ mapView: NavigationMapView) -> CGPoint { - self.delegate?.navigationViewController?(self, mapViewUserAnchorPoint: mapView) ?? .zero + self.delegate?.navigationViewController(self, mapViewUserAnchorPoint: mapView) ?? .zero } func mapViewControllerShouldAnnotateSpokenInstructions(_ routeMapViewController: RouteMapViewController) -> Bool { @@ -598,47 +578,58 @@ extension NavigationViewController: RouteMapViewControllerDelegate { } func mapViewController(_ mapViewController: RouteMapViewController, roadNameAt location: CLLocation) -> String? { - guard let roadName = delegate?.navigationViewController?(self, roadNameAt: location) else { + guard let roadName = delegate?.navigationViewController(self, roadNameAt: location) else { return nil } return roadName } public func label(_ label: InstructionLabel, willPresent instruction: VisualInstruction, as presented: NSAttributedString) -> NSAttributedString? { - self.delegate?.label?(label, willPresent: instruction, as: presented) + self.delegate?.label(label, willPresent: instruction, as: presented) } } // MARK: - RouteControllerDelegate extension NavigationViewController: RouteControllerDelegate { + public func routeController(_ routeController: MapboxCoreNavigation.RouteController, shouldPreventReroutesWhenArrivingAt waypoint: MapboxDirections.Waypoint) -> Bool { + false + } + + public func routeControllerShouldDisableBatteryMonitoring(_ routeController: MapboxCoreNavigation.RouteController) -> Bool { + false + } + + public func routeControllerGetDirections(from location: CLLocation, along progress: MapboxCoreNavigation.RouteProgress, completion: @escaping (MapboxDirections.Route?, [MapboxDirections.Route]?, (any Error)?) -> Void) {} + public func routeController(_ routeController: RouteController, shouldRerouteFrom location: CLLocation) -> Bool { - self.delegate?.navigationViewController?(self, shouldRerouteFrom: location) ?? true + self.delegate?.navigationViewController(self, shouldRerouteFrom: location) ?? true } public func routeController(_ routeController: RouteController, willRerouteFrom location: CLLocation) { - self.delegate?.navigationViewController?(self, willRerouteFrom: location) + self.delegate?.navigationViewController(self, willRerouteFrom: location) } - @objc public func routeController(_ routeController: RouteController, didRerouteAlong route: Route, reason: RouteController.RerouteReason) { self.mapViewController.notifyDidReroute(route: route) - self.delegate?.navigationViewController?(self, didRerouteAlong: route) + self.delegate?.navigationViewController(self, didRerouteAlong: route) } public func routeController(_ routeController: RouteController, didFailToRerouteWith error: Error) { - self.delegate?.navigationViewController?(self, didFailToRerouteWith: error) + self.delegate?.navigationViewController(self, didFailToRerouteWith: error) } public func routeController(_ routeController: RouteController, shouldDiscard location: CLLocation) -> Bool { - self.delegate?.navigationViewController?(self, shouldDiscard: location) ?? true + self.delegate?.navigationViewController(self, shouldDiscard: location) ?? true } public func routeController(_ routeController: RouteController, didUpdate locations: [CLLocation]) { // If the user has arrived, don't snap the user puck. // In the case the user drives beyond the waypoint, // we should accurately depict this. - let shouldPreventReroutesWhenArrivingAtWaypoint = routeController.delegate?.routeController?(routeController, shouldPreventReroutesWhenArrivingAt: routeController.routeProgress.currentLeg.destination) ?? true + guard let waypoint = routeController.routeProgress.currentLeg.destination else { return } + + let shouldPreventReroutesWhenArrivingAtWaypoint = routeController.delegate?.routeController(routeController, shouldPreventReroutesWhenArrivingAt: waypoint) ?? true let userHasArrivedAndShouldPreventRerouting = shouldPreventReroutesWhenArrivingAtWaypoint && !routeController.routeProgress.currentLegProgress.userHasArrivedAtWaypoint if self.snapsUserLocationAnnotationToRoute, @@ -652,7 +643,7 @@ extension NavigationViewController: RouteControllerDelegate { } public func routeController(_ routeController: RouteController, didArriveAt waypoint: Waypoint) -> Bool { - let advancesToNextLeg = self.delegate?.navigationViewController?(self, didArriveAt: waypoint) ?? true + let advancesToNextLeg = self.delegate?.navigationViewController(self, didArriveAt: waypoint) ?? true if !self.isConnectedToCarPlay, // CarPlayManager shows rating on CarPlay if it's connected routeController.routeProgress.isFinalLeg, advancesToNextLeg { @@ -661,7 +652,7 @@ extension NavigationViewController: RouteControllerDelegate { return } self.mapViewController.hideEndOfRoute { _ in - self.delegate?.navigationViewControllerDidFinishRouting?(self) + self.delegate?.navigationViewControllerDidFinishRouting(self) } }) } diff --git a/MapboxNavigation/RouteMapViewController.swift b/MapboxNavigation/RouteMapViewController.swift index c16efb13..fe7d663a 100644 --- a/MapboxNavigation/RouteMapViewController.swift +++ b/MapboxNavigation/RouteMapViewController.swift @@ -8,7 +8,7 @@ import UIKit class ArrowFillPolyline: MLNPolylineFeature {} class ArrowStrokePolyline: ArrowFillPolyline {} -@objc protocol RouteMapViewControllerDelegate: NavigationMapViewDelegate, MLNMapViewDelegate, VisualInstructionDelegate { +protocol RouteMapViewControllerDelegate: NavigationMapViewDelegate, MLNMapViewDelegate, VisualInstructionDelegate { func mapViewControllerDidFinish(_ mapViewController: RouteMapViewController, byCanceling canceled: Bool) func mapViewControllerShouldAnnotateSpokenInstructions(_ routeMapViewController: RouteMapViewController) -> Bool @@ -21,7 +21,7 @@ class ArrowStrokePolyline: ArrowFillPolyline {} - parameter location: The user’s current location. - return: The road name to display in the label, or the empty string to hide the label, or nil to query the map’s vector tiles for the road name. */ - @objc func mapViewController(_ mapViewController: RouteMapViewController, roadNameAt location: CLLocation) -> String? + func mapViewController(_ mapViewController: RouteMapViewController, roadNameAt location: CLLocation) -> String? } class RouteMapViewController: UIViewController { diff --git a/Package.swift b/Package.swift index 8209ce58..5aa8b6fe 100644 --- a/Package.swift +++ b/Package.swift @@ -15,8 +15,8 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/flitsmeister/mapbox-directions-swift", exact: "0.23.3"), - .package(url: "https://github.com/flitsmeister/turf-swift", exact: "0.2.2"), + .package(url: "https://github.com/mapbox/mapbox-directions-swift", exact: "2.14.0"), + .package(url: "https://github.com/mapbox/turf-swift.git", "2.8.0" ..< "2.9.0"), .package(url: "https://github.com/maplibre/maplibre-gl-native-distribution.git", from: "6.0.0"), .package(url: "https://github.com/ceeK/Solar.git", exact: "3.0.1"), .package(url: "https://github.com/nicklockwood/SwiftFormat.git", from: "0.53.6")