From d9f92ade33e5e6c90bc74b4a211a13f2b1139c5d Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Mon, 9 Nov 2020 18:14:44 -0800 Subject: [PATCH 01/48] Add callout annotations showing route durations for comparing alternate routes during preview. --- Example/Assets.xcassets/Contents.json | 6 +- .../RouteAnnotations/Contents.json | 6 + .../Contents.json | 15 ++ .../LeftHandUpDown.pdf | Bin 0 -> 5380 bytes .../Contents.json | 29 +++ .../RightHandUpDown.pdf | Bin 0 -> 5375 bytes Example/ViewController.swift | 212 +++++++++++++++++- Extensions/Locale+Example.swift | 13 ++ Extensions/MGLMapView+Example.swift | 8 + Extensions/Route+Example.swift | 53 +++++ Extensions/Turf+Example.swift | 27 +++ Extensions/UIImage+Example.swift | 28 +++ MapboxNavigation.xcodeproj/project.pbxproj | 28 +++ 13 files changed, 421 insertions(+), 4 deletions(-) create mode 100644 Example/Assets.xcassets/RouteAnnotations/Contents.json create mode 100644 Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/Contents.json create mode 100644 Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/LeftHandUpDown.pdf create mode 100644 Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/Contents.json create mode 100644 Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/RightHandUpDown.pdf create mode 100644 Extensions/Locale+Example.swift create mode 100644 Extensions/MGLMapView+Example.swift create mode 100644 Extensions/Route+Example.swift create mode 100644 Extensions/Turf+Example.swift create mode 100644 Extensions/UIImage+Example.swift diff --git a/Example/Assets.xcassets/Contents.json b/Example/Assets.xcassets/Contents.json index da4a164c918..73c00596a7f 100644 --- a/Example/Assets.xcassets/Contents.json +++ b/Example/Assets.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Example/Assets.xcassets/RouteAnnotations/Contents.json b/Example/Assets.xcassets/RouteAnnotations/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/Example/Assets.xcassets/RouteAnnotations/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/Contents.json b/Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/Contents.json new file mode 100644 index 00000000000..50bc330dd19 --- /dev/null +++ b/Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "LeftHandUpDown.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/LeftHandUpDown.pdf b/Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/LeftHandUpDown.pdf new file mode 100644 index 0000000000000000000000000000000000000000..374d23c3b25dbf228bf686c4e0ab3b46efebfcdd GIT binary patch literal 5380 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 M4?P7{b<`mL1Ezh<#sB~S literal 0 HcmV?d00001 diff --git a/Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/Contents.json b/Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/Contents.json new file mode 100644 index 00000000000..d9628090e07 --- /dev/null +++ b/Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/Contents.json @@ -0,0 +1,29 @@ +{ + "images" : [ + { + "filename" : "RightHandUpDown.pdf", + "idiom" : "universal", + "resizing" : { + "cap-insets" : { + "bottom" : 22, + "left" : 11, + "right" : 11, + "top" : 22 + }, + "center" : { + "height" : 2, + "mode" : "stretch", + "width" : 1 + }, + "mode" : "9-part" + } + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/RightHandUpDown.pdf b/Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/RightHandUpDown.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0f0da450e6080083844cff784f5ee695905fb0af GIT binary patch literal 5375 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 O3|a;%EUc!Z4*efDkI8NT literal 0 HcmV?d00001 diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 6aa6ed0f894..fd7ea624dea 100755 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -2,6 +2,7 @@ import UIKit import MapboxCoreNavigation import MapboxNavigation import MapboxDirections +import Turf private typealias RouteRequestSuccess = ((RouteResponse) -> Void) private typealias RouteRequestFailure = ((Error) -> Void) @@ -48,8 +49,198 @@ class ViewController: UIViewController { startButton.isEnabled = true mapView?.show(routes) mapView?.showWaypoints(on: currentRoute) + if let style = mapView?.style { + // show route Duration and Toll annotations + updateAnnotationSymbolImages(style) + updateRouteAnnotations(routes, style: style) + } + } + } + + fileprivate let dateComponentsFormatter = DateComponentsFormatter() + + // Regenerate the annotation "bubble" images for any changes in dynamic UIColors. + private func updateAnnotationSymbolImages(_ style: MGLStyle) { + let capInsetHeight = CGFloat(22) + let capInsetWidth = CGFloat(11) + let capInsets = UIEdgeInsets(top: capInsetHeight, left: capInsetWidth, bottom: capInsetHeight, right: capInsetWidth) + if let image = UIImage(named: "RouteInfoAnnotationLeftHanded") { + let regularRouteImage = image.tint(UIColor.white).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) + style.setImage(regularRouteImage, forName: "RouteInfoAnnotationLeftHanded") + + let selectedRouteImage = image.tint(#colorLiteral(red: 0.337254902, green: 0.6588235294, blue: 0.9843137255, alpha: 1)).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) + style.setImage(selectedRouteImage, forName: "RouteInfoAnnotationLeftHanded-Selected") + } + + if let image = UIImage(named: "RouteInfoAnnotationRightHanded") { + let regularRouteImage = image.tint(UIColor.white).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) + style.setImage(regularRouteImage, forName: "RouteInfoAnnotationRightHanded") + + let selectedRouteImage = image.tint(#colorLiteral(red: 0.337254902, green: 0.6588235294, blue: 0.9843137255, alpha: 1)).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) + style.setImage(selectedRouteImage, forName: "RouteInfoAnnotationRightHanded-Selected") } } + + private func removeRouteAnnotationsLayerFromStyle(_ style: MGLStyle) { + if let annotationsLayer = style.layer(withIdentifier: annotationLayerIdentifier + "-shape") { + style.removeLayer(annotationsLayer) + } + + if let annotationsSource = style.source(withIdentifier: annotationLayerIdentifier + "-source") { + style.removeSource(annotationsSource) + } + } + + let annotationLayerIdentifier = "RouteETAAnnotations" + + private func updateRouteAnnotations(_ routes: [Route]?, style: MGLStyle) { + // remove any existing route annotation + removeRouteAnnotationsLayerFromStyle(style) + + guard waypoints.count > 0, let routes = routes, let mapView = mapView else { return } + + let visibleCoordinateBounds = mapView.coordinateBoundsInset(CGSize(width: 40, height: 60)) + let visibleBoundingBox = BoundingBox(coordinateBounds: visibleCoordinateBounds) + + let tollRoutes = routes.filter { route -> Bool in + return (route.tollIntersections?.count ?? 0) > 0 + } + let routesContainTolls = tollRoutes.count > 0 + + guard let selectedRoute = routes.first else { return } + + guard let visibleSelectedRoute = selectedRoute.shapes(within: visibleBoundingBox), let selectedRouteShape = visibleSelectedRoute.first else { return } + + let selectedRouteCoordinate = selectedRouteShape.coordinateAtNormalizedPosition(0.25)! + + var selectedRouteTailPosition = RouteETAAnnotationTailPosition.right + + + if let currentLocation = mapView.userLocation?.location?.coordinate { + selectedRouteTailPosition = selectedRouteCoordinate.longitude > currentLocation.longitude ? .left : .right + } + + var features = [MGLPointFeature]() + for (index, route) in routes.dropFirst().enumerated() { + guard let visibleShapes = route.shapes(within: visibleBoundingBox), let visibleSegment = visibleShapes.first else { continue } + + let lineOffset = index % 2 == 0 ? Double(0.75) : Double(0.25) + + var coordinate = kCLLocationCoordinate2DInvalid + + if #available(iOS 13.0, *) { + let elementCoordinates = visibleSegment.coordinates + + var difference = [CLLocationCoordinate2D]() + var matching = [CLLocationCoordinate2D]() + + for coordinate in elementCoordinates { + if let indexedCoordinate = selectedRouteShape.closestCoordinate(to: coordinate) { + if coordinate.distance(to: indexedCoordinate.coordinate) > 10 { + difference.append(coordinate) + } else { + matching.append(coordinate) + } + } + } + + let line = LineString(difference) + + if let distance = line.distance(), let earlyCoordinate = line.coordinateFromStart(distance: distance * 0.25 ), let midCoordinate = line.coordinateFromStart(distance: distance * 0.5), let lateCoordinate = line.coordinateFromStart(distance: distance * 0.75) { + coordinate = selectedRouteCoordinate.distance(to: earlyCoordinate) > selectedRouteCoordinate.distance(to: lateCoordinate) ? earlyCoordinate : lateCoordinate + + coordinate = selectedRouteCoordinate.distance(to: coordinate) > selectedRouteCoordinate.distance(to: midCoordinate) ? coordinate : midCoordinate + } else { + coordinate = line.coordinateFromStart(distance: route.distance * lineOffset) ?? kCLLocationCoordinate2DInvalid + } + } else { + coordinate = route.shape?.coordinateFromStart(distance: route.distance * lineOffset) ?? kCLLocationCoordinate2DInvalid + } + + let text = annotationLabelForRoute(route, tolls: routesContainTolls) + + let point = MGLPointFeature() + point.coordinate = coordinate + let tailPosition = selectedRouteTailPosition == .left ? RouteETAAnnotationTailPosition.right : RouteETAAnnotationTailPosition.left + let imageName = tailPosition == .left ? "RouteInfoAnnotationLeftHanded" : "RouteInfoAnnotationRightHanded" + point.attributes = ["selected": false, "tailPosition": tailPosition.rawValue, "text": text, "imageName": imageName] + + features.append(point) + } + + let text = annotationLabelForRoute(selectedRoute, tolls: routesContainTolls) + + let point = MGLPointFeature() + point.coordinate = selectedRouteCoordinate + point.attributes = ["selected": true, "tailPosition": selectedRouteTailPosition.rawValue, "text": text, "imageName": selectedRouteTailPosition == .left ? "RouteInfoAnnotationLeftHanded-Selected" : "RouteInfoAnnotationRightHanded-Selected"] + + features.append(point) + + let dataSource: MGLShapeSource + if let source = style.source(withIdentifier: annotationLayerIdentifier + "-source") as? MGLShapeSource { + dataSource = source + } else { + dataSource = MGLShapeSource(identifier: annotationLayerIdentifier + "-source", features: features, options: nil) + style.addSource(dataSource) + } + + let shapeLayer: MGLSymbolStyleLayer + + if let layer = style.layer(withIdentifier: annotationLayerIdentifier + "-shape") as? MGLSymbolStyleLayer { + shapeLayer = layer + } else { + shapeLayer = MGLSymbolStyleLayer(identifier: annotationLayerIdentifier + "-shape", source: dataSource) + } + + shapeLayer.text = NSExpression(forKeyPath: "text") + let fontSizeByZoomLevel = [ + 13: NSExpression(forConstantValue: 16), + 15.5: NSExpression(forConstantValue: 20) + ] + shapeLayer.textFontSize = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", fontSizeByZoomLevel) + + shapeLayer.textColor = NSExpression(forConditional: NSPredicate(format: "selected == true"), + trueExpression: NSExpression(forConstantValue: UIColor.white), + falseExpression: NSExpression(forConstantValue: UIColor.black)) + + shapeLayer.textFontNames = NSExpression(forConstantValue: ["DIN Pro Medium"]) + shapeLayer.textAllowsOverlap = NSExpression(forConstantValue: false) + shapeLayer.textJustification = NSExpression(forConstantValue: "left") + shapeLayer.symbolZOrder = NSExpression(forConstantValue: NSValue(mglSymbolZOrder: .auto)) + shapeLayer.symbolSortKey = NSExpression(forConditional: NSPredicate(format: "selected == true"), + trueExpression: NSExpression(forConstantValue: 0), + falseExpression: NSExpression(forConstantValue: 1)) + shapeLayer.iconAnchor = NSExpression(forConditional: NSPredicate(format: "tailPosition == 0"), + trueExpression: NSExpression(forConstantValue: "bottom-left"), + falseExpression: NSExpression(forConstantValue: "bottom-right")) + shapeLayer.textAnchor = shapeLayer.iconAnchor + shapeLayer.iconTextFit = NSExpression(forConstantValue: "both") + + shapeLayer.iconImageName = NSExpression(forKeyPath: "imageName") + shapeLayer.iconOffset = NSExpression(forConditional: NSPredicate(format: "tailPosition == 0"), + trueExpression: NSExpression(forConstantValue: CGVector(dx: 0.5, dy: -1.0)), + falseExpression: NSExpression(forConstantValue: CGVector(dx: -0.5, dy: -1.0))) + shapeLayer.textOffset = shapeLayer.iconOffset + shapeLayer.iconAllowsOverlap = NSExpression(forConstantValue: true) + + style.addLayer(shapeLayer) + } + + private func annotationLabelForRoute(_ route: Route, tolls: Bool) -> String { + var eta = dateComponentsFormatter.string(from: route.expectedTravelTime) ?? "" + + let hasTolls = (route.tollIntersections?.count ?? 0) > 0 + if hasTolls { + eta += "\nTolls" + if let symbol = Locale.current.currencySymbol { + eta += " " + symbol + } + } else if tolls { + eta += "\nNo Tolls" + } + + return eta + } weak var activeNavigationViewController: NavigationViewController? @@ -94,6 +285,10 @@ class ViewController: UIViewController { if let appDelegate = UIApplication.shared.delegate as? AppDelegate { appDelegate.currentAppRootViewController = self } + + dateComponentsFormatter.maximumUnitCount = 3 + dateComponentsFormatter.allowedUnits = [.hour, .minute] + dateComponentsFormatter.unitsStyle = .short } deinit { @@ -185,6 +380,10 @@ class ViewController: UIViewController { startButton.isEnabled = false clearMap.isHidden = true longPressHintView.isHidden = false + + if let style = mapView?.style { + removeRouteAnnotationsLayerFromStyle(style) + } mapView?.unhighlightBuildings() mapView?.removeRoutes() @@ -420,7 +619,7 @@ extension ViewController: MGLMapViewDelegate { guard mapView == self.mapView else { return } - + self.mapView?.localizeLabels() if let routes = response?.routes, let currentRoute = routes.first, let coords = currentRoute.shape?.coordinates { @@ -452,6 +651,12 @@ extension ViewController: MGLMapViewDelegate { } } } + + func mapView(_ mapView: MGLMapView, regionDidChangeWith reason: MGLCameraChangeReason, animated: Bool) { + if let style = mapView.style, let routes = response?.routes { + updateRouteAnnotations(routes, style: style) + } + } } // MARK: - NavigationMapViewDelegate @@ -612,3 +817,8 @@ extension ViewController { mapView?.addAnnotations([rawTrackPolyline!, trackPolyline!]) } } + +enum RouteETAAnnotationTailPosition: Int { + case left + case right +} diff --git a/Extensions/Locale+Example.swift b/Extensions/Locale+Example.swift new file mode 100644 index 00000000000..427efd4e5fa --- /dev/null +++ b/Extensions/Locale+Example.swift @@ -0,0 +1,13 @@ +import Foundation + +extension Locale { + var currencySymbol: String? { + guard let currencyCode = currencyCode else { return nil } + let locale = NSLocale(localeIdentifier: currencyCode) + if locale.displayName(forKey: .currencySymbol, value: currencyCode) == currencyCode { + let newlocale = NSLocale(localeIdentifier: currencyCode.dropLast() + "_en") + return newlocale.displayName(forKey: .currencySymbol, value: currencyCode) + } + return locale.displayName(forKey: .currencySymbol, value: currencyCode) + } +} diff --git a/Extensions/MGLMapView+Example.swift b/Extensions/MGLMapView+Example.swift new file mode 100644 index 00000000000..9210d7eaf1b --- /dev/null +++ b/Extensions/MGLMapView+Example.swift @@ -0,0 +1,8 @@ +import Foundation +import Mapbox + +extension MGLMapView { + func coordinateBoundsInset(_ inset: CGSize) -> MGLCoordinateBounds { + return convert(bounds.insetBy(dx: inset.width, dy: inset.height), toCoordinateBoundsFrom: nil) + } +} diff --git a/Extensions/Route+Example.swift b/Extensions/Route+Example.swift new file mode 100644 index 00000000000..ca7b16189d6 --- /dev/null +++ b/Extensions/Route+Example.swift @@ -0,0 +1,53 @@ +import Foundation +import MapboxDirections +import Turf + +extension Route { + var tollIntersections: [Intersection]? { + return nil // Stubbed out until SDK includes new Intersection toll attributes + #if NavSDK_Includes_Toll_Intersections + let allSteps = legs.compactMap { return $0.steps }.reduce([], +) + + let allIntersections = allSteps.compactMap { return $0.intersections }.reduce([], +) + let intersectionsWithTolls = allIntersections.filter { return $0.tollCollection != nil } + + return intersectionsWithTolls + #endif + } + + // returns the list of line segments along the route that fall within given bounding box. Returns nil if there are none. Line segments are defined by the route shape coordinates that lay within the bounding box + func shapes(within: Turf.BoundingBox) -> [LineString]? { + guard let coordinates = shape?.coordinates else { return nil } + var lines = [[CLLocationCoordinate2D]]() + var currentLine: [CLLocationCoordinate2D]? + for coordinate in coordinates { + // see if this coordinate lays within the bounds + if within.contains(coordinate) { + // if there is no current line segment then start one + if currentLine == nil { + currentLine = [CLLocationCoordinate2D]() + } + + // append the coordinate to the current line segment + currentLine?.append(coordinate) + } else { + // if there is a current line segment being built then finish it off and reset + if let currentLine = currentLine { + lines.append(currentLine) + } + currentLine = nil + } + } + + // append any outstanding final segment + if let currentLine = currentLine { + lines.append(currentLine) + } + currentLine = nil + + // return the segments as LineStrings + return lines.compactMap { coordinateList -> LineString? in + return LineString(coordinateList) + } + } +} diff --git a/Extensions/Turf+Example.swift b/Extensions/Turf+Example.swift new file mode 100644 index 00000000000..cd8d49d7166 --- /dev/null +++ b/Extensions/Turf+Example.swift @@ -0,0 +1,27 @@ +import Foundation +import Mapbox +import Turf + +extension Turf.BoundingBox { + init(coordinateBounds: MGLCoordinateBounds) { + self.init(coordinateBounds.sw, coordinateBounds.ne) + } +} + +extension LineString { + var midpoint: CLLocationCoordinate2D? { + if let distance = self.distance(), let midpoint = self.coordinateFromStart(distance: distance/2) { + return midpoint + } + + return nil + } + + func coordinateAtNormalizedPosition(_ position: Double) -> CLLocationCoordinate2D? { + if let distance = self.distance(), let point = self.coordinateFromStart(distance: distance * position) { + return point + } + + return nil + } +} diff --git a/Extensions/UIImage+Example.swift b/Extensions/UIImage+Example.swift new file mode 100644 index 00000000000..0480f249308 --- /dev/null +++ b/Extensions/UIImage+Example.swift @@ -0,0 +1,28 @@ +import UIKit + +extension UIImage { + func tint(_ tintColor: UIColor) -> UIImage { + let imageSize = size + let imageScale = scale + let contextBounds = CGRect(origin: .zero, size: imageSize) + + UIGraphicsBeginImageContextWithOptions(imageSize, false, imageScale) + + defer { UIGraphicsEndImageContext() } + + UIColor.black.setFill() + UIRectFill(contextBounds) + draw(at: .zero) + + guard let imageOverBlack = UIGraphicsGetImageFromCurrentImageContext() else { return self } + tintColor.setFill() + UIRectFill(contextBounds) + + imageOverBlack.draw(at: .zero, blendMode: .multiply, alpha: 1) + draw(at: .zero, blendMode: .destinationIn, alpha: 1) + + guard let finalImage = UIGraphicsGetImageFromCurrentImageContext() else { return self } + + return finalImage + } +} diff --git a/MapboxNavigation.xcodeproj/project.pbxproj b/MapboxNavigation.xcodeproj/project.pbxproj index 29b9468ee54..03f6805385f 100644 --- a/MapboxNavigation.xcodeproj/project.pbxproj +++ b/MapboxNavigation.xcodeproj/project.pbxproj @@ -402,6 +402,11 @@ DADD82802161EC0300B8B47D /* UIViewAnimationOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADD827F2161EC0300B8B47D /* UIViewAnimationOptionsTests.swift */; }; DAE22A2921C9DEDA00CA269D /* MGLVectorTileSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE22A2821C9DEDA00CA269D /* MGLVectorTileSourceTests.swift */; }; DAFA92071F01735000A7FB09 /* DistanceFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 351BEC0B1E5BCC72006FE110 /* DistanceFormatter.swift */; }; + F4052062255A16DC00F3DD47 /* MGLMapView+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052061255A16DC00F3DD47 /* MGLMapView+Example.swift */; }; + F405206A255A171800F3DD47 /* Turf+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052069255A171800F3DD47 /* Turf+Example.swift */; }; + F4052084255A178C00F3DD47 /* Route+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052083255A178C00F3DD47 /* Route+Example.swift */; }; + F405208C255A17D300F3DD47 /* Locale+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F405208B255A17D300F3DD47 /* Locale+Example.swift */; }; + F405209A255A180500F3DD47 /* UIImage+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052099255A180500F3DD47 /* UIImage+Example.swift */; }; F4BF512E24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BF512D24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift */; }; F4C5A26F24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C5A26E24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift */; }; /* End PBXBuildFile section */ @@ -1046,6 +1051,11 @@ DAFEB36D2093A11F00A86A83 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; DAFEB36E2093A3E000A86A83 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; DAFEB36F2093A3EF00A86A83 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ko; path = Resources/ko.lproj/Localizable.stringsdict; sourceTree = ""; }; + F4052061255A16DC00F3DD47 /* MGLMapView+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MGLMapView+Example.swift"; sourceTree = ""; }; + F4052069255A171800F3DD47 /* Turf+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Turf+Example.swift"; sourceTree = ""; }; + F4052083255A178C00F3DD47 /* Route+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Route+Example.swift"; sourceTree = ""; }; + F405208B255A17D300F3DD47 /* Locale+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+Example.swift"; sourceTree = ""; }; + F4052099255A180500F3DD47 /* UIImage+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Example.swift"; sourceTree = ""; }; F4BF512D24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackSubtypeCollectionViewCell.swift; sourceTree = ""; }; F4C5A26E24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackSubtypeViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1422,6 +1432,7 @@ 3529FCF121A5C5B300AEA9AA /* ResizableView.swift */, 3529FCF521A5C5D900AEA9AA /* UIViewController.swift */, 3529FCF721A5C62400AEA9AA /* SettingsViewController.swift */, + F40520A1255A183B00F3DD47 /* Extensions */, ); name = Example; sourceTree = ""; @@ -1855,6 +1866,18 @@ path = Sources/CMapboxCoreNavigation; sourceTree = ""; }; + F40520A1255A183B00F3DD47 /* Extensions */ = { + isa = PBXGroup; + children = ( + F4052061255A16DC00F3DD47 /* MGLMapView+Example.swift */, + F4052069255A171800F3DD47 /* Turf+Example.swift */, + F4052083255A178C00F3DD47 /* Route+Example.swift */, + F405208B255A17D300F3DD47 /* Locale+Example.swift */, + F4052099255A180500F3DD47 /* UIImage+Example.swift */, + ); + path = Extensions; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -2652,13 +2675,18 @@ 358D14681E5E3B7700ADE590 /* ViewController.swift in Sources */, 3529FCF821A5C62400AEA9AA /* SettingsViewController.swift in Sources */, 3529FCF421A5C5C600AEA9AA /* SettingsItems.swift in Sources */, + F4052084255A178C00F3DD47 /* Route+Example.swift in Sources */, 3529FCF221A5C5B400AEA9AA /* ResizableView.swift in Sources */, C5D9800D1EFA8BA9006DBF2E /* CustomViewController.swift in Sources */, + F405206A255A171800F3DD47 /* Turf+Example.swift in Sources */, + F405209A255A180500F3DD47 /* UIImage+Example.swift in Sources */, AED6285622CBE4CE00058A51 /* ViewController+GuidanceCards.swift in Sources */, C51FC31720F689F800400CE7 /* CustomStyles.swift in Sources */, 6441B16A1EFC64E50076499F /* WaypointConfirmationViewController.swift in Sources */, + F405208C255A17D300F3DD47 /* Locale+Example.swift in Sources */, 358D14661E5E3B7700ADE590 /* AppDelegate.swift in Sources */, 3529FCF621A5C5D900AEA9AA /* UIViewController.swift in Sources */, + F4052062255A16DC00F3DD47 /* MGLMapView+Example.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 5e27ec133566a70afb4ac74bcac916d3139a5c33 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Wed, 11 Nov 2020 11:03:33 -0800 Subject: [PATCH 02/48] Move to method using RouteSteps as a way to find a spot to put annotations. --- Example/ViewController.swift | 79 ++++++++++++++------------ Extensions/MGLMapView+Example.swift | 68 +++++++++++++++++++++++ Extensions/Route+Example.swift | 86 +++++++++++++++++++++++++++++ Extensions/Turf+Example.swift | 34 ++++++++++++ 4 files changed, 231 insertions(+), 36 deletions(-) diff --git a/Example/ViewController.swift b/Example/ViewController.swift index fd7ea624dea..8a7a354e41c 100755 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -111,54 +111,50 @@ class ViewController: UIViewController { guard let visibleSelectedRoute = selectedRoute.shapes(within: visibleBoundingBox), let selectedRouteShape = visibleSelectedRoute.first else { return } - let selectedRouteCoordinate = selectedRouteShape.coordinateAtNormalizedPosition(0.25)! + guard let selectedRouteCoordinate = selectedRouteShape.coordinateAtNormalizedPosition(0.25) else { return } var selectedRouteTailPosition = RouteETAAnnotationTailPosition.right - if let currentLocation = mapView.userLocation?.location?.coordinate { selectedRouteTailPosition = selectedRouteCoordinate.longitude > currentLocation.longitude ? .left : .right } var features = [MGLPointFeature]() - for (index, route) in routes.dropFirst().enumerated() { - guard let visibleShapes = route.shapes(within: visibleBoundingBox), let visibleSegment = visibleShapes.first else { continue } - - let lineOffset = index % 2 == 0 ? Double(0.75) : Double(0.25) - - var coordinate = kCLLocationCoordinate2DInvalid - if #available(iOS 13.0, *) { - let elementCoordinates = visibleSegment.coordinates - - var difference = [CLLocationCoordinate2D]() - var matching = [CLLocationCoordinate2D]() - - for coordinate in elementCoordinates { - if let indexedCoordinate = selectedRouteShape.closestCoordinate(to: coordinate) { - if coordinate.distance(to: indexedCoordinate.coordinate) > 10 { - difference.append(coordinate) - } else { - matching.append(coordinate) - } + // we will look for a set of RouteSteps unique to each alternate route, then find a coordinate along that line + // to use as the position of the annotation callout + var excludedSteps = selectedRoute.legs.compactMap { return $0.steps }.reduce([], +) + for (index, route) in routes.dropFirst().enumerated() { + let allSteps = route.legs.compactMap { return $0.steps }.reduce([], +) + let alternateSteps = allSteps.filter { step -> Bool in + for existingStep in excludedSteps { + if step == existingStep { + return false } } + return true + } - let line = LineString(difference) + excludedSteps.append(contentsOf: alternateSteps) + let visibleAlternateSteps = alternateSteps.filter { $0.intersects(visibleBoundingBox) } - if let distance = line.distance(), let earlyCoordinate = line.coordinateFromStart(distance: distance * 0.25 ), let midCoordinate = line.coordinateFromStart(distance: distance * 0.5), let lateCoordinate = line.coordinateFromStart(distance: distance * 0.75) { - coordinate = selectedRouteCoordinate.distance(to: earlyCoordinate) > selectedRouteCoordinate.distance(to: lateCoordinate) ? earlyCoordinate : lateCoordinate +// for step in visibleAlternateSteps { +// guard let shape = step.shape else { continue } +// style.addDebugLineLayer(identifier: UUID().uuidString, coordinates: shape.coordinates, color: UIColor.random) +// } - coordinate = selectedRouteCoordinate.distance(to: coordinate) > selectedRouteCoordinate.distance(to: midCoordinate) ? coordinate : midCoordinate - } else { - coordinate = line.coordinateFromStart(distance: route.distance * lineOffset) ?? kCLLocationCoordinate2DInvalid - } - } else { - coordinate = route.shape?.coordinateFromStart(distance: route.distance * lineOffset) ?? kCLLocationCoordinate2DInvalid + var coordinate = kCLLocationCoordinate2DInvalid + + let lineOffset = index % 2 == 0 ? 0.35 : 0.75 + if let continuousLine = visibleAlternateSteps.continuousShapeFromFirstElement(), let distance = continuousLine.distance(), let routelineCoordinate = continuousLine.coordinateFromStart(distance: distance * lineOffset) { + coordinate = routelineCoordinate + + style.addDebugLineLayer(identifier: UUID().uuidString, coordinates: continuousLine.coordinates, color: .random) } let text = annotationLabelForRoute(route, tolls: routesContainTolls) + // Create the feature for this route annotation. Set the styling attributes that will be used to render the annotation in the style layer. let point = MGLPointFeature() point.coordinate = coordinate let tailPosition = selectedRouteTailPosition == .left ? RouteETAAnnotationTailPosition.right : RouteETAAnnotationTailPosition.left @@ -176,6 +172,10 @@ class ViewController: UIViewController { features.append(point) + addRouteAnnotationLayer(features: features, style: style) + } + + private func addRouteAnnotationLayer(features: [MGLPointFeature], style: MGLStyle) { let dataSource: MGLShapeSource if let source = style.source(withIdentifier: annotationLayerIdentifier + "-source") as? MGLShapeSource { dataSource = source @@ -204,12 +204,12 @@ class ViewController: UIViewController { falseExpression: NSExpression(forConstantValue: UIColor.black)) shapeLayer.textFontNames = NSExpression(forConstantValue: ["DIN Pro Medium"]) - shapeLayer.textAllowsOverlap = NSExpression(forConstantValue: false) + shapeLayer.textAllowsOverlap = NSExpression(forConstantValue: true) shapeLayer.textJustification = NSExpression(forConstantValue: "left") - shapeLayer.symbolZOrder = NSExpression(forConstantValue: NSValue(mglSymbolZOrder: .auto)) + shapeLayer.symbolZOrder = NSExpression(forConstantValue: NSValue(mglSymbolZOrder: MGLSymbolZOrder.auto)) shapeLayer.symbolSortKey = NSExpression(forConditional: NSPredicate(format: "selected == true"), - trueExpression: NSExpression(forConstantValue: 0), - falseExpression: NSExpression(forConstantValue: 1)) + trueExpression: NSExpression(forConstantValue: 1), + falseExpression: NSExpression(forConstantValue: 0)) shapeLayer.iconAnchor = NSExpression(forConditional: NSPredicate(format: "tailPosition == 0"), trueExpression: NSExpression(forConstantValue: "bottom-left"), falseExpression: NSExpression(forConstantValue: "bottom-right")) @@ -231,12 +231,12 @@ class ViewController: UIViewController { let hasTolls = (route.tollIntersections?.count ?? 0) > 0 if hasTolls { - eta += "\nTolls" + eta += "\n" + NSLocalizedString("ROUTE_HAS_TOLLS", value: "Tolls", comment: "This route does have tolls") if let symbol = Locale.current.currencySymbol { eta += " " + symbol } } else if tolls { - eta += "\nNo Tolls" + eta += "\n" + NSLocalizedString("ROUTE_HAS_NO_TOLLS", value: "No Tolls", comment: "This route does not have tolls") } return eta @@ -383,6 +383,7 @@ class ViewController: UIViewController { if let style = mapView?.style { removeRouteAnnotationsLayerFromStyle(style) + style.removeDebugLineLayers() } mapView?.unhighlightBuildings() @@ -822,3 +823,9 @@ enum RouteETAAnnotationTailPosition: Int { case left case right } + +extension UIColor { + static var random: UIColor { + return UIColor(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1), alpha: 1.0) + } +} diff --git a/Extensions/MGLMapView+Example.swift b/Extensions/MGLMapView+Example.swift index 9210d7eaf1b..c47916f1059 100644 --- a/Extensions/MGLMapView+Example.swift +++ b/Extensions/MGLMapView+Example.swift @@ -1,8 +1,76 @@ import Foundation import Mapbox +import Turf extension MGLMapView { func coordinateBoundsInset(_ inset: CGSize) -> MGLCoordinateBounds { return convert(bounds.insetBy(dx: inset.width, dy: inset.height), toCoordinateBoundsFrom: nil) } } + + +extension MGLStyle { + func addDebugLineLayer(identifier: String, coordinates: [CLLocationCoordinate2D], color: UIColor = UIColor.purple) { +// removeDebugLineLayers() + + let lineString = LineString(coordinates) + let lineFeature = MGLPolylineFeature(lineString) + let shapeSource = MGLShapeSource(identifier: "addDebugLineLayer" + identifier, features: [lineFeature], options: nil) + addSource(shapeSource) + + let lineLayer = MGLLineStyleLayer(identifier: "addDebugLineLayer" + identifier, source: shapeSource) + lineLayer.lineColor = NSExpression(forConstantValue: color) + lineLayer.lineWidth = NSExpression(forConstantValue: 24) + lineLayer.lineCap = NSExpression(forConstantValue: "round") + addLayer(lineLayer) + } + + func addDebugPolygonLayer(identifier: String, coordinates: [CLLocationCoordinate2D], color: UIColor = UIColor.purple) { + removeDebugFillLayers() + + let fillFeature = MGLPolygonFeature(coordinates: coordinates, count: UInt(coordinates.count)) + let shapeSource = MGLShapeSource(identifier: "addDebugPolygonLayer" + identifier, features: [fillFeature], options: nil) + addSource(shapeSource) + + let fillLayer = MGLFillStyleLayer(identifier: "addDebugPolygonLayer" + identifier, source: shapeSource) + fillLayer.fillColor = NSExpression(forConstantValue: color) + fillLayer.fillOpacity = NSExpression(forConstantValue: NSNumber(0.25)) + fillLayer.fillOutlineColor = NSExpression(forConstantValue: color) + fillLayer.fillOpacity = NSExpression(forConstantValue: NSNumber(0.75)) + addLayer(fillLayer) + } + + func removeDebugLineLayers() { + // remove any old layers + for lineLayer in layers.filter({ layer -> Bool in + guard let layer = layer as? MGLLineStyleLayer else { return false } + return layer.identifier.contains("addDebugLineLayer") + }) { + removeLayer(lineLayer) + } + + // remove any old sources + for dataSource in sources.filter({ source -> Bool in + return source.identifier.contains("addDebugLineLayer") + }) { + removeSource(dataSource) + } + } + + func removeDebugFillLayers() { + // remove any old layers + for lineLayer in layers.filter({ layer -> Bool in + guard let layer = layer as? MGLFillStyleLayer else { return false } + return layer.identifier.contains("addDebugPolygonLayer") + }) { + removeLayer(lineLayer) + } + + // remove any old sources + for dataSource in sources.filter({ source -> Bool in + return source.identifier.contains("addDebugPolygonLayer") + }) { + removeSource(dataSource) + } + } +} diff --git a/Extensions/Route+Example.swift b/Extensions/Route+Example.swift index ca7b16189d6..1ae4ecef4ca 100644 --- a/Extensions/Route+Example.swift +++ b/Extensions/Route+Example.swift @@ -15,6 +15,11 @@ extension Route { #endif } + func stepsIntersecting(boundingBox: Turf.BoundingBox) -> [RouteStep]? { + let steps = legs.compactMap { return $0.steps }.reduce([], +) + return steps.filter { return $0.intersects(boundingBox) } + } + // returns the list of line segments along the route that fall within given bounding box. Returns nil if there are none. Line segments are defined by the route shape coordinates that lay within the bounding box func shapes(within: Turf.BoundingBox) -> [LineString]? { guard let coordinates = shape?.coordinates else { return nil } @@ -50,4 +55,85 @@ extension Route { return LineString(coordinateList) } } + + func stepsNotAlong(routeShape: LineString) -> [RouteStep]? { + let steps = legs.compactMap { return $0.steps }.reduce([], +) + let stepsNotOnRoute = steps.filter { + let beginsOn = $0.beginsOn(routeShape) + if !beginsOn { return true } + let endsOn = $0.endsOn(routeShape) + if !endsOn { return true } + return false + } + return stepsNotOnRoute + } +} + +extension RouteStep { + + func intersects(_ boundingBox: Turf.BoundingBox) -> Bool { + guard let coordinates = shape?.coordinates else { return false } + + for coordinate in coordinates { + if boundingBox.contains(coordinate) { + return true + } + } + return false + } + + var initialCoordinate: CLLocationCoordinate2D? { + return shape?.coordinates.first + } + + var finalCoordinate: CLLocationCoordinate2D? { + return shape?.coordinates.last + } + + func beginsOn(_ line: LineString, tolerance: CLLocationDistance = 10) -> Bool { + + guard let initialCoordinate = initialCoordinate, let closestCoordinate = line.closestCoordinate(to: initialCoordinate) else { return false } + + + let distanceAtStart = closestCoordinate.coordinate.distance(to: initialCoordinate) + return distanceAtStart <= tolerance + } + + func endsOn(_ line: LineString, tolerance: CLLocationDistance = 10) -> Bool { + + guard let finalCoordinate = finalCoordinate, let closestCoordinate = line.closestCoordinate(to: finalCoordinate) else { return false } + + + let distanceAtStart = closestCoordinate.coordinate.distance(to: finalCoordinate) + return distanceAtStart <= tolerance + } +} + +extension CLLocationCoordinate2D: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(latitude) + hasher.combine(longitude) + } +} +extension RouteStep: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(shape?.coordinates) + } +} + +extension Array where Element == RouteStep { + func continuousShapeFromFirstElement() -> LineString? { + guard count > 0 else { return nil } + guard count > 1 else { return self[0].shape } + var continuousLine = [CLLocationCoordinate2D]() + + for index in 0...count-2 { + if let currentStepFinalCoordinate = self[index].finalCoordinate, currentStepFinalCoordinate.distance(to: self[index+1].maneuverLocation) < 10, let coordinates = self[index].shape?.coordinates { + continuousLine.append(contentsOf: coordinates) + } + } + + return LineString(continuousLine) + } } + diff --git a/Extensions/Turf+Example.swift b/Extensions/Turf+Example.swift index cd8d49d7166..d42e17e020c 100644 --- a/Extensions/Turf+Example.swift +++ b/Extensions/Turf+Example.swift @@ -24,4 +24,38 @@ extension LineString { return nil } + + func segmentsIntersecting(boundingBox: Turf.BoundingBox) -> [LineString]? { + var lines = [[CLLocationCoordinate2D]]() + var currentLine: [CLLocationCoordinate2D]? + for coordinate in coordinates { + // see if this coordinate lays within the bounds + if boundingBox.contains(coordinate) { + // if there is no current line segment then start one + if currentLine == nil { + currentLine = [CLLocationCoordinate2D]() + } + + // append the coordinate to the current line segment + currentLine?.append(coordinate) + } else { + // if there is a current line segment being built then finish it off and reset + if let currentLine = currentLine { + lines.append(currentLine) + } + currentLine = nil + } + } + + // append any outstanding final segment + if let currentLine = currentLine { + lines.append(currentLine) + } + currentLine = nil + + // return the segments as LineStrings + return lines.compactMap { coordinateList -> LineString? in + return LineString(coordinateList) + } + } } From 877a48a057885d2a907a47b0f09dc8f8f23101b1 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Wed, 11 Nov 2020 11:58:47 -0800 Subject: [PATCH 03/48] Turn off debug line. Add some randomness to the placement of route annotations. Set symbol sort order based on index of routes in the array. More comments. --- Example/ViewController.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 8a7a354e41c..718e6b19667 100755 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -145,11 +145,11 @@ class ViewController: UIViewController { var coordinate = kCLLocationCoordinate2DInvalid - let lineOffset = index % 2 == 0 ? 0.35 : 0.75 + let lineOffset = index % 2 == 0 ? Double.random(in: 0...0.3) : Double.random(in: 0.65...0.8) if let continuousLine = visibleAlternateSteps.continuousShapeFromFirstElement(), let distance = continuousLine.distance(), let routelineCoordinate = continuousLine.coordinateFromStart(distance: distance * lineOffset) { coordinate = routelineCoordinate - style.addDebugLineLayer(identifier: UUID().uuidString, coordinates: continuousLine.coordinates, color: .random) +// style.addDebugLineLayer(identifier: UUID().uuidString, coordinates: continuousLine.coordinates, color: .random) } let text = annotationLabelForRoute(route, tolls: routesContainTolls) @@ -159,7 +159,7 @@ class ViewController: UIViewController { point.coordinate = coordinate let tailPosition = selectedRouteTailPosition == .left ? RouteETAAnnotationTailPosition.right : RouteETAAnnotationTailPosition.left let imageName = tailPosition == .left ? "RouteInfoAnnotationLeftHanded" : "RouteInfoAnnotationRightHanded" - point.attributes = ["selected": false, "tailPosition": tailPosition.rawValue, "text": text, "imageName": imageName] + point.attributes = ["selected": false, "tailPosition": tailPosition.rawValue, "text": text, "imageName": imageName, "sortOrder": -index] features.append(point) } @@ -209,7 +209,7 @@ class ViewController: UIViewController { shapeLayer.symbolZOrder = NSExpression(forConstantValue: NSValue(mglSymbolZOrder: MGLSymbolZOrder.auto)) shapeLayer.symbolSortKey = NSExpression(forConditional: NSPredicate(format: "selected == true"), trueExpression: NSExpression(forConstantValue: 1), - falseExpression: NSExpression(forConstantValue: 0)) + falseExpression: NSExpression(format: "sortOrder")) shapeLayer.iconAnchor = NSExpression(forConditional: NSPredicate(format: "tailPosition == 0"), trueExpression: NSExpression(forConstantValue: "bottom-left"), falseExpression: NSExpression(forConstantValue: "bottom-right")) @@ -236,6 +236,8 @@ class ViewController: UIViewController { eta += " " + symbol } } else if tolls { + // If one of the routes has tolls, but this one does not then it needs to explicitly say that it has no tolls + // If no routes have tolls at all then we can omit this portion of the string. eta += "\n" + NSLocalizedString("ROUTE_HAS_NO_TOLLS", value: "No Tolls", comment: "This route does not have tolls") } From c445d391f0e5b4f1357f959569005c10d3093e89 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Tue, 17 Nov 2020 11:26:09 -0800 Subject: [PATCH 04/48] Improve Route Annotation layout logic. Add route shape simplification for faster calculations. --- Example/Simplify.swift | 201 +++++++++++++++++++++ Example/ViewController.swift | 70 +++++-- Extensions/MGLMapView+Example.swift | 37 +++- Extensions/Route+Example.swift | 4 +- Extensions/Turf+Example.swift | 5 + MapboxNavigation.xcodeproj/project.pbxproj | 6 + 6 files changed, 304 insertions(+), 19 deletions(-) create mode 100644 Example/Simplify.swift diff --git a/Example/Simplify.swift b/Example/Simplify.swift new file mode 100644 index 00000000000..5fb4b3ec850 --- /dev/null +++ b/Example/Simplify.swift @@ -0,0 +1,201 @@ +// +// Simplify.swift +// +// Copyright (c) 2018 Tomislav Filipcic +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation +import CoreLocation +import CoreGraphics + +public protocol SimplifyValue { + var xValue: Double { get } + var yValue: Double { get } +} + +func equalsPoints(l: T, r: T) -> Bool { + return l.xValue == r.xValue && l.yValue == r.yValue +} + +extension CGPoint: SimplifyValue { + + public var xValue: Double { + return Double(x) + } + + public var yValue: Double { + return Double(y) + } +} + +extension CLLocationCoordinate2D: SimplifyValue { + + public var xValue: Double { + return latitude + } + + public var yValue: Double { + return longitude + } +} + +open class Simplify { + /** + Calculate square distance + + - parameter pointA: from point + - parameter pointB: to point + + - returns: square distance between two points + */ + fileprivate class func getSquareDistance(_ pointA: T,_ pointB: T) -> Float { + return Float((pointA.xValue - pointB.xValue) * (pointA.xValue - pointB.xValue) + (pointA.yValue - pointB.yValue) * (pointA.yValue - pointB.yValue)) + } + + /** + Calculate square distance from a point to a segment + + - parameter point: from point + - parameter seg1: segment first point + - parameter seg2: segment last point + + - returns: square distance between point to a segment + */ + fileprivate class func getSquareSegmentDistance(point p: T, seg1 s1: T, seg2 s2: T) -> Float { + + var x = s1.xValue + var y = s1.yValue + var dx = s2.xValue - x + var dy = s2.yValue - y + + if dx != 0 || dy != 0 { + let t = ((p.xValue - x) * dx + (p.yValue - y) * dy) / ((dx * dx) + (dy * dy)) + if t > 1 { + x = s2.xValue + y = s2.yValue + } else if t > 0 { + x += dx * t + y += dy * t + } + } + + dx = p.xValue - x + dy = p.yValue - y + + return Float((dx * dx) + (dy * dy)) + } + + /** + Simplify an array of points using the Ramer-Douglas-Peucker algorithm + + - parameter points: An array of points + - parameter tolerance: Affects the amount of simplification (in the same metric as the point coordinates) + + - returns: Returns an array of simplified points + */ + fileprivate class func simplifyDouglasPeucker(_ points: [T], tolerance: Float!) -> [T] { + if points.count <= 2 { + return points + } + + let lastPoint: Int = points.count - 1 + var result: [T] = [points.first!] + simplifyDouglasPeuckerStep(points, first: 0, last: lastPoint, tolerance: tolerance, simplified: &result) + result.append(points[lastPoint]) + return result + } + + fileprivate class func simplifyDouglasPeuckerStep(_ points: [T], first: Int, last: Int, tolerance: Float, simplified: inout [T]) { + var maxSquareDistance = tolerance + var index = 0 + + for i in first + 1 ..< last { + let sqDist = getSquareSegmentDistance(point: points[i], seg1: points[first], seg2: points[last]) + if sqDist > maxSquareDistance { + index = i + maxSquareDistance = sqDist + } + } + + if maxSquareDistance > tolerance { + if index - first > 1 { + simplifyDouglasPeuckerStep(points, first: first, last: index, tolerance: tolerance, simplified: &simplified) + } + simplified.append(points[index]) + if last - index > 1 { + simplifyDouglasPeuckerStep(points, first: index, last: last, tolerance: tolerance, simplified: &simplified) + } + } + } + + /** + Simplify an array of points using the Radial Distance algorithm + + - parameter points: An array of points + - parameter tolerance: Affects the amount of simplification (in the same metric as the point coordinates) + + - returns: Returns an array of simplified points + */ + fileprivate class func simplifyRadialDistance(_ points: [T], tolerance: Float!) -> [T] { + if points.count <= 2 { + return points + } + + var prevPoint: T = points.first! + var newPoints: [T] = [prevPoint] + var point: T = points[1] + + for idx in 1 ..< points.count { + point = points[idx] + let distance = getSquareDistance(point, prevPoint) + if distance > tolerance! { + newPoints.append(point) + prevPoint = point + } + } + + if !equalsPoints(l: prevPoint, r: point) { + newPoints.append(point) + } + + return newPoints + } + + /** + Returns an array of simplified points + + - parameter points: An array of points + - parameter tolerance: Affects the amount of simplification (in the same metric as the point coordinates) + - parameter highQuality: Excludes distance-based preprocessing step which leads to highest quality simplification but runs ~10-20 times slower + + - returns: Returns an array of simplified points + */ + + open class func simplify(_ points: [T], tolerance: Float?, highQuality: Bool = false) -> [T] { + if points.count <= 2 { + return points + } + + let squareTolerance = (tolerance != nil ? tolerance! * tolerance! : 1.0) + var result: [T] = (highQuality == true ? points : simplifyRadialDistance(points, tolerance: squareTolerance)) + result = simplifyDouglasPeucker(result, tolerance: squareTolerance) + return result + } +} diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 718e6b19667..8616b310672 100755 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -111,13 +111,9 @@ class ViewController: UIViewController { guard let visibleSelectedRoute = selectedRoute.shapes(within: visibleBoundingBox), let selectedRouteShape = visibleSelectedRoute.first else { return } - guard let selectedRouteCoordinate = selectedRouteShape.coordinateAtNormalizedPosition(0.25) else { return } + guard let selectedRouteCoordinate = selectedRouteShape.coordinateAtNormalizedPosition(Double.random(in: 0.10...0.3)) else { return } - var selectedRouteTailPosition = RouteETAAnnotationTailPosition.right - - if let currentLocation = mapView.userLocation?.location?.coordinate { - selectedRouteTailPosition = selectedRouteCoordinate.longitude > currentLocation.longitude ? .left : .right - } + let selectedRouteTailPosition = mapView.convert(selectedRouteCoordinate, toPointTo: nil).x <= mapView.bounds.width / 2 ? RouteETAAnnotationTailPosition.left : RouteETAAnnotationTailPosition.right var features = [MGLPointFeature]() @@ -145,19 +141,60 @@ class ViewController: UIViewController { var coordinate = kCLLocationCoordinate2DInvalid - let lineOffset = index % 2 == 0 ? Double.random(in: 0...0.3) : Double.random(in: 0.65...0.8) - if let continuousLine = visibleAlternateSteps.continuousShapeFromFirstElement(), let distance = continuousLine.distance(), let routelineCoordinate = continuousLine.coordinateFromStart(distance: distance * lineOffset) { - coordinate = routelineCoordinate + if let continuousLine = visibleAlternateSteps.continuousShape(), continuousLine.coordinates.count > 0 { + coordinate = continuousLine.coordinates[0]//routelineCoordinate + + // Simplify LineStrings with many vertices + let simplifiedLine = continuousLine.coordinates.count < 100 ? continuousLine : continuousLine.simplified +// style.addDebugLineLayer(identifier: UUID().uuidString, coordinates: simplifiedLine.coordinates, color: .random) + + var furthestDistance: CLLocationDistance = 0 + var furthestVertex = kCLLocationCoordinate2DInvalid + for vertex in simplifiedLine.coordinates { + let distanceToSelectedRoute = vertex.distance(to: selectedRouteCoordinate) + + if distanceToSelectedRoute > furthestDistance { + furthestDistance = distanceToSelectedRoute + furthestVertex = vertex + } + } + + let distanceSortedVertices = simplifiedLine.coordinates.sorted { + $0.distance(to: selectedRouteCoordinate) < $1.distance(to: selectedRouteCoordinate) + } + +// for (index, vertex) in distanceSortedVertices.enumerated() { +// style.addDebugCircleLayer(identifier: UUID().uuidString, coordinate: vertex, color: UIColor(white: CGFloat(index) / CGFloat(distanceSortedVertices.count), alpha: 1.0)) +// } -// style.addDebugLineLayer(identifier: UUID().uuidString, coordinates: continuousLine.coordinates, color: .random) + for vertex in distanceSortedVertices { + print(vertex.distance(to: selectedRouteCoordinate)) + if vertex.distance(to: selectedRouteCoordinate) >= furthestDistance * 0.75 { + furthestVertex = vertex + break + } + } + + if furthestVertex != kCLLocationCoordinate2DInvalid { + coordinate = continuousLine.closestCoordinate(to: furthestVertex)?.coordinate ?? furthestVertex + } } let text = annotationLabelForRoute(route, tolls: routesContainTolls) + let unprojectedCoordinate = mapView.convert(coordinate, toPointTo: nil) + // Create the feature for this route annotation. Set the styling attributes that will be used to render the annotation in the style layer. let point = MGLPointFeature() point.coordinate = coordinate - let tailPosition = selectedRouteTailPosition == .left ? RouteETAAnnotationTailPosition.right : RouteETAAnnotationTailPosition.left + var tailPosition = selectedRouteTailPosition == .left ? RouteETAAnnotationTailPosition.right : RouteETAAnnotationTailPosition.left + + if tailPosition == .left && unprojectedCoordinate.x > mapView.bounds.width * 0.75 { + tailPosition = .right + } else if tailPosition == .right && unprojectedCoordinate.x < mapView.bounds.width * 0.25 { + tailPosition = .left + } + let imageName = tailPosition == .left ? "RouteInfoAnnotationLeftHanded" : "RouteInfoAnnotationRightHanded" point.attributes = ["selected": false, "tailPosition": tailPosition.rawValue, "text": text, "imageName": imageName, "sortOrder": -index] @@ -386,6 +423,7 @@ class ViewController: UIViewController { if let style = mapView?.style { removeRouteAnnotationsLayerFromStyle(style) style.removeDebugLineLayers() + style.removeDebugCircleLayers() } mapView?.unhighlightBuildings() @@ -655,11 +693,11 @@ extension ViewController: MGLMapViewDelegate { } } - func mapView(_ mapView: MGLMapView, regionDidChangeWith reason: MGLCameraChangeReason, animated: Bool) { - if let style = mapView.style, let routes = response?.routes { - updateRouteAnnotations(routes, style: style) - } - } +// func mapView(_ mapView: MGLMapView, regionDidChangeWith reason: MGLCameraChangeReason, animated: Bool) { +// if let style = mapView.style, let routes = response?.routes { +// updateRouteAnnotations(routes, style: style) +// } +// } } // MARK: - NavigationMapViewDelegate diff --git a/Extensions/MGLMapView+Example.swift b/Extensions/MGLMapView+Example.swift index c47916f1059..9242e4275dc 100644 --- a/Extensions/MGLMapView+Example.swift +++ b/Extensions/MGLMapView+Example.swift @@ -10,6 +10,24 @@ extension MGLMapView { extension MGLStyle { + + func addDebugCircleLayer(identifier: String, coordinate: CLLocationCoordinate2D, color: UIColor = UIColor.purple) { + let point = MGLPointFeature() + point.coordinate = coordinate + + let dataSource = MGLShapeSource(identifier: "addDebugCircleLayer" + identifier, features: [point], options: nil) + addSource(dataSource) + + let circle = MGLCircleStyleLayer(identifier: "addDebugCircleLayer" + identifier, source: dataSource) + circle.circleRadius = NSExpression(forConstantValue: 10) + circle.circleOpacity = NSExpression(forConstantValue: 0.75) + circle.circleColor = NSExpression(forConstantValue: color) + circle.circleStrokeWidth = NSExpression(forConstantValue: NSNumber(4)) + circle.circleStrokeColor = NSExpression(forConstantValue: UIColor.white) + + addLayer(circle) + } + func addDebugLineLayer(identifier: String, coordinates: [CLLocationCoordinate2D], color: UIColor = UIColor.purple) { // removeDebugLineLayers() @@ -20,7 +38,7 @@ extension MGLStyle { let lineLayer = MGLLineStyleLayer(identifier: "addDebugLineLayer" + identifier, source: shapeSource) lineLayer.lineColor = NSExpression(forConstantValue: color) - lineLayer.lineWidth = NSExpression(forConstantValue: 24) + lineLayer.lineWidth = NSExpression(forConstantValue: 8) lineLayer.lineCap = NSExpression(forConstantValue: "round") addLayer(lineLayer) } @@ -73,4 +91,21 @@ extension MGLStyle { removeSource(dataSource) } } + + func removeDebugCircleLayers() { + // remove any old layers + for lineLayer in layers.filter({ layer -> Bool in + guard let layer = layer as? MGLCircleStyleLayer else { return false } + return layer.identifier.contains("addDebugCircleLayer") + }) { + removeLayer(lineLayer) + } + + // remove any old sources + for dataSource in sources.filter({ source -> Bool in + return source.identifier.contains("addDebugCircleLayer") + }) { + removeSource(dataSource) + } + } } diff --git a/Extensions/Route+Example.swift b/Extensions/Route+Example.swift index 1ae4ecef4ca..442f49ad920 100644 --- a/Extensions/Route+Example.swift +++ b/Extensions/Route+Example.swift @@ -122,13 +122,13 @@ extension RouteStep: Hashable { } extension Array where Element == RouteStep { - func continuousShapeFromFirstElement() -> LineString? { + func continuousShape(tolerance: CLLocationDistance = 100) -> LineString? { guard count > 0 else { return nil } guard count > 1 else { return self[0].shape } var continuousLine = [CLLocationCoordinate2D]() for index in 0...count-2 { - if let currentStepFinalCoordinate = self[index].finalCoordinate, currentStepFinalCoordinate.distance(to: self[index+1].maneuverLocation) < 10, let coordinates = self[index].shape?.coordinates { + if let currentStepFinalCoordinate = self[index].finalCoordinate, currentStepFinalCoordinate.distance(to: self[index+1].maneuverLocation) < tolerance, let coordinates = self[index].shape?.coordinates { continuousLine.append(contentsOf: coordinates) } } diff --git a/Extensions/Turf+Example.swift b/Extensions/Turf+Example.swift index d42e17e020c..e5415056ce6 100644 --- a/Extensions/Turf+Example.swift +++ b/Extensions/Turf+Example.swift @@ -58,4 +58,9 @@ extension LineString { return LineString(coordinateList) } } + + var simplified: LineString { + let simplifiedCoordinates = Simplify.simplify(coordinates, tolerance: 0.001) + return LineString(simplifiedCoordinates) + } } diff --git a/MapboxNavigation.xcodeproj/project.pbxproj b/MapboxNavigation.xcodeproj/project.pbxproj index 03f6805385f..ee174c2c0ac 100644 --- a/MapboxNavigation.xcodeproj/project.pbxproj +++ b/MapboxNavigation.xcodeproj/project.pbxproj @@ -407,6 +407,8 @@ F4052084255A178C00F3DD47 /* Route+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052083255A178C00F3DD47 /* Route+Example.swift */; }; F405208C255A17D300F3DD47 /* Locale+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F405208B255A17D300F3DD47 /* Locale+Example.swift */; }; F405209A255A180500F3DD47 /* UIImage+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052099255A180500F3DD47 /* UIImage+Example.swift */; }; + F4A415F7255DEAD500373466 /* Simplify.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A415F6255DEAD500373466 /* Simplify.swift */; }; + F4A415F8255DEAD500373466 /* Simplify.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A415F6255DEAD500373466 /* Simplify.swift */; }; F4BF512E24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BF512D24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift */; }; F4C5A26F24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C5A26E24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift */; }; /* End PBXBuildFile section */ @@ -1056,6 +1058,7 @@ F4052083255A178C00F3DD47 /* Route+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Route+Example.swift"; sourceTree = ""; }; F405208B255A17D300F3DD47 /* Locale+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+Example.swift"; sourceTree = ""; }; F4052099255A180500F3DD47 /* UIImage+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Example.swift"; sourceTree = ""; }; + F4A415F6255DEAD500373466 /* Simplify.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Simplify.swift; path = Example/Simplify.swift; sourceTree = ""; }; F4BF512D24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackSubtypeCollectionViewCell.swift; sourceTree = ""; }; F4C5A26E24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackSubtypeViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1419,6 +1422,7 @@ 35B711DB1E5E7B70001EDA8D /* Example */ = { isa = PBXGroup; children = ( + F4A415F6255DEAD500373466 /* Simplify.swift */, 35002D721E5F6C830090E733 /* Supporting files */, 358D14651E5E3B7700ADE590 /* AppDelegate.swift */, 35379CFB21480BFB00FD402E /* AppDelegate+CarPlay.swift */, @@ -2678,6 +2682,7 @@ F4052084255A178C00F3DD47 /* Route+Example.swift in Sources */, 3529FCF221A5C5B400AEA9AA /* ResizableView.swift in Sources */, C5D9800D1EFA8BA9006DBF2E /* CustomViewController.swift in Sources */, + F4A415F7255DEAD500373466 /* Simplify.swift in Sources */, F405206A255A171800F3DD47 /* Turf+Example.swift in Sources */, F405209A255A180500F3DD47 /* UIImage+Example.swift in Sources */, AED6285622CBE4CE00058A51 /* ViewController+GuidanceCards.swift in Sources */, @@ -2791,6 +2796,7 @@ 35379CFD21480C0500FD402E /* AppDelegate+CarPlay.swift in Sources */, C53F2EE820EBC95600D9798F /* AppDelegate.swift in Sources */, DA303CA621B7A90100F921DC /* UIViewController.swift in Sources */, + F4A415F8255DEAD500373466 /* Simplify.swift in Sources */, 3577B878214FF35800094294 /* FavoritesList.swift in Sources */, DA303CA921B7A93100F921DC /* SettingsItems.swift in Sources */, DA303CA721B7A91C00F921DC /* SettingsViewController.swift in Sources */, From 95b771c20ab9f64593a3e527879c69a7ec48a946 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Tue, 17 Nov 2020 13:02:02 -0800 Subject: [PATCH 05/48] Remove some debug code. --- Example/ViewController.swift | 37 ++++++++++-------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 8616b310672..3de34a9516d 100755 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -7,6 +7,11 @@ import Turf private typealias RouteRequestSuccess = ((RouteResponse) -> Void) private typealias RouteRequestFailure = ((Error) -> Void) +private enum RouteETAAnnotationTailPosition: Int { + case left + case right +} + class ViewController: UIViewController { // MARK: - IBOutlets @IBOutlet weak var longPressHintView: UIView! @@ -134,11 +139,6 @@ class ViewController: UIViewController { excludedSteps.append(contentsOf: alternateSteps) let visibleAlternateSteps = alternateSteps.filter { $0.intersects(visibleBoundingBox) } -// for step in visibleAlternateSteps { -// guard let shape = step.shape else { continue } -// style.addDebugLineLayer(identifier: UUID().uuidString, coordinates: shape.coordinates, color: UIColor.random) -// } - var coordinate = kCLLocationCoordinate2DInvalid if let continuousLine = visibleAlternateSteps.continuousShape(), continuousLine.coordinates.count > 0 { @@ -146,7 +146,6 @@ class ViewController: UIViewController { // Simplify LineStrings with many vertices let simplifiedLine = continuousLine.coordinates.count < 100 ? continuousLine : continuousLine.simplified -// style.addDebugLineLayer(identifier: UUID().uuidString, coordinates: simplifiedLine.coordinates, color: .random) var furthestDistance: CLLocationDistance = 0 var furthestVertex = kCLLocationCoordinate2DInvalid @@ -163,12 +162,7 @@ class ViewController: UIViewController { $0.distance(to: selectedRouteCoordinate) < $1.distance(to: selectedRouteCoordinate) } -// for (index, vertex) in distanceSortedVertices.enumerated() { -// style.addDebugCircleLayer(identifier: UUID().uuidString, coordinate: vertex, color: UIColor(white: CGFloat(index) / CGFloat(distanceSortedVertices.count), alpha: 1.0)) -// } - for vertex in distanceSortedVertices { - print(vertex.distance(to: selectedRouteCoordinate)) if vertex.distance(to: selectedRouteCoordinate) >= furthestDistance * 0.75 { furthestVertex = vertex break @@ -693,11 +687,11 @@ extension ViewController: MGLMapViewDelegate { } } -// func mapView(_ mapView: MGLMapView, regionDidChangeWith reason: MGLCameraChangeReason, animated: Bool) { -// if let style = mapView.style, let routes = response?.routes { -// updateRouteAnnotations(routes, style: style) -// } -// } + func mapView(_ mapView: MGLMapView, regionDidChangeWith reason: MGLCameraChangeReason, animated: Bool) { + if let style = mapView.style, let routes = response?.routes { + updateRouteAnnotations(routes, style: style) + } + } } // MARK: - NavigationMapViewDelegate @@ -858,14 +852,3 @@ extension ViewController { mapView?.addAnnotations([rawTrackPolyline!, trackPolyline!]) } } - -enum RouteETAAnnotationTailPosition: Int { - case left - case right -} - -extension UIColor { - static var random: UIColor { - return UIColor(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1), alpha: 1.0) - } -} From d80edf70b2ca0a0edb3eb5883376234a2956bf38 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Tue, 17 Nov 2020 16:27:43 -0800 Subject: [PATCH 06/48] More cleanup including removing unneeded extensions. --- Example/ViewController.swift | 71 +++++++++++++++++----------------- Extensions/Route+Example.swift | 56 ++------------------------- 2 files changed, 39 insertions(+), 88 deletions(-) diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 3de34a9516d..7144f518c0e 100755 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -56,7 +56,6 @@ class ViewController: UIViewController { mapView?.showWaypoints(on: currentRoute) if let style = mapView?.style { // show route Duration and Toll annotations - updateAnnotationSymbolImages(style) updateRouteAnnotations(routes, style: style) } } @@ -64,7 +63,6 @@ class ViewController: UIViewController { fileprivate let dateComponentsFormatter = DateComponentsFormatter() - // Regenerate the annotation "bubble" images for any changes in dynamic UIColors. private func updateAnnotationSymbolImages(_ style: MGLStyle) { let capInsetHeight = CGFloat(22) let capInsetWidth = CGFloat(11) @@ -86,18 +84,6 @@ class ViewController: UIViewController { } } - private func removeRouteAnnotationsLayerFromStyle(_ style: MGLStyle) { - if let annotationsLayer = style.layer(withIdentifier: annotationLayerIdentifier + "-shape") { - style.removeLayer(annotationsLayer) - } - - if let annotationsSource = style.source(withIdentifier: annotationLayerIdentifier + "-source") { - style.removeSource(annotationsSource) - } - } - - let annotationLayerIdentifier = "RouteETAAnnotations" - private func updateRouteAnnotations(_ routes: [Route]?, style: MGLStyle) { // remove any existing route annotation removeRouteAnnotationsLayerFromStyle(style) @@ -122,7 +108,7 @@ class ViewController: UIViewController { var features = [MGLPointFeature]() - // we will look for a set of RouteSteps unique to each alternate route, then find a coordinate along that line + // we will look for a set of RouteSteps unique to each alternate route, then find a coordinate along that portion of the route line // to use as the position of the annotation callout var excludedSteps = selectedRoute.legs.compactMap { return $0.steps }.reduce([], +) for (index, route) in routes.dropFirst().enumerated() { @@ -141,27 +127,22 @@ class ViewController: UIViewController { var coordinate = kCLLocationCoordinate2DInvalid + // Obtain a polyline of the set of steps. We'll look for a good spot along this line to place the annotation if let continuousLine = visibleAlternateSteps.continuousShape(), continuousLine.coordinates.count > 0 { - coordinate = continuousLine.coordinates[0]//routelineCoordinate + coordinate = continuousLine.coordinates[0] - // Simplify LineStrings with many vertices + // We don't need a full resolution polyline in order to find our spot so simplify and complex shapes with many vertices let simplifiedLine = continuousLine.coordinates.count < 100 ? continuousLine : continuousLine.simplified - var furthestDistance: CLLocationDistance = 0 - var furthestVertex = kCLLocationCoordinate2DInvalid - for vertex in simplifiedLine.coordinates { - let distanceToSelectedRoute = vertex.distance(to: selectedRouteCoordinate) - - if distanceToSelectedRoute > furthestDistance { - furthestDistance = distanceToSelectedRoute - furthestVertex = vertex - } - } - + // find the vertex that is the furthest from the location of the selected route's annotation let distanceSortedVertices = simplifiedLine.coordinates.sorted { $0.distance(to: selectedRouteCoordinate) < $1.distance(to: selectedRouteCoordinate) } + let furthestDistance = distanceSortedVertices.last?.distance(to: selectedRouteCoordinate) ?? 0 + var furthestVertex = kCLLocationCoordinate2DInvalid + + // look for a vertex that is "far enough" from the selected annotation coordinate. We do this so we don't always put the annotations at the end of the route line. for vertex in distanceSortedVertices { if vertex.distance(to: selectedRouteCoordinate) >= furthestDistance * 0.75 { furthestVertex = vertex @@ -174,8 +155,9 @@ class ViewController: UIViewController { } } - let text = annotationLabelForRoute(route, tolls: routesContainTolls) + let labelText = annotationLabelForRoute(route, tolls: routesContainTolls) + // convert our coordinate to screen space so we make some choices on which side of the coordinate the label ends up on let unprojectedCoordinate = mapView.convert(coordinate, toPointTo: nil) // Create the feature for this route annotation. Set the styling attributes that will be used to render the annotation in the style layer. @@ -183,6 +165,7 @@ class ViewController: UIViewController { point.coordinate = coordinate var tailPosition = selectedRouteTailPosition == .left ? RouteETAAnnotationTailPosition.right : RouteETAAnnotationTailPosition.left + // pick the orientation of the bubble stem based on how close to the edge of the screen it is if tailPosition == .left && unprojectedCoordinate.x > mapView.bounds.width * 0.75 { tailPosition = .right } else if tailPosition == .right && unprojectedCoordinate.x < mapView.bounds.width * 0.25 { @@ -190,23 +173,26 @@ class ViewController: UIViewController { } let imageName = tailPosition == .left ? "RouteInfoAnnotationLeftHanded" : "RouteInfoAnnotationRightHanded" - point.attributes = ["selected": false, "tailPosition": tailPosition.rawValue, "text": text, "imageName": imageName, "sortOrder": -index] + point.attributes = ["selected": false, "tailPosition": tailPosition.rawValue, "text": labelText, "imageName": imageName, "sortOrder": -index] features.append(point) } - let text = annotationLabelForRoute(selectedRoute, tolls: routesContainTolls) + // add the annotation for the selected annotation last so it ends up ordered on top of the others + let labelText = annotationLabelForRoute(selectedRoute, tolls: routesContainTolls) let point = MGLPointFeature() point.coordinate = selectedRouteCoordinate - point.attributes = ["selected": true, "tailPosition": selectedRouteTailPosition.rawValue, "text": text, "imageName": selectedRouteTailPosition == .left ? "RouteInfoAnnotationLeftHanded-Selected" : "RouteInfoAnnotationRightHanded-Selected"] - + point.attributes = ["selected": true, "tailPosition": selectedRouteTailPosition.rawValue, "text": labelText, "imageName": selectedRouteTailPosition == .left ? "RouteInfoAnnotationLeftHanded-Selected" : "RouteInfoAnnotationRightHanded-Selected"] features.append(point) - addRouteAnnotationLayer(features: features, style: style) + // add the features to the style + addRouteAnnotationSymbolLayer(features: features, style: style) } - private func addRouteAnnotationLayer(features: [MGLPointFeature], style: MGLStyle) { + private let annotationLayerIdentifier = "RouteETAAnnotations" + + private func addRouteAnnotationSymbolLayer(features: [MGLPointFeature], style: MGLStyle) { let dataSource: MGLShapeSource if let source = style.source(withIdentifier: annotationLayerIdentifier + "-source") as? MGLShapeSource { dataSource = source @@ -257,6 +243,17 @@ class ViewController: UIViewController { style.addLayer(shapeLayer) } + private func removeRouteAnnotationsLayerFromStyle(_ style: MGLStyle) { + if let annotationsLayer = style.layer(withIdentifier: annotationLayerIdentifier + "-shape") { + style.removeLayer(annotationsLayer) + } + + if let annotationsSource = style.source(withIdentifier: annotationLayerIdentifier + "-source") { + style.removeSource(annotationsSource) + } + } + + // This function generates the text for the label to be shown on screen. It will include estimated duration and info on Tolls, if applicable private func annotationLabelForRoute(_ route: Route, tolls: Bool) -> String { var eta = dateComponentsFormatter.string(from: route.expectedTravelTime) ?? "" @@ -656,7 +653,9 @@ extension ViewController: MGLMapViewDelegate { } self.mapView?.localizeLabels() - + + self.updateAnnotationSymbolImages(style) + if let routes = response?.routes, let currentRoute = routes.first, let coords = currentRoute.shape?.coordinates { mapView.setVisibleCoordinateBounds(MGLPolygon(coordinates: coords, count: UInt(coords.count)).overlayBounds, animated: false) self.mapView?.show(routes) diff --git a/Extensions/Route+Example.swift b/Extensions/Route+Example.swift index 442f49ad920..c37ca3ff162 100644 --- a/Extensions/Route+Example.swift +++ b/Extensions/Route+Example.swift @@ -55,22 +55,9 @@ extension Route { return LineString(coordinateList) } } - - func stepsNotAlong(routeShape: LineString) -> [RouteStep]? { - let steps = legs.compactMap { return $0.steps }.reduce([], +) - let stepsNotOnRoute = steps.filter { - let beginsOn = $0.beginsOn(routeShape) - if !beginsOn { return true } - let endsOn = $0.endsOn(routeShape) - if !endsOn { return true } - return false - } - return stepsNotOnRoute - } } extension RouteStep { - func intersects(_ boundingBox: Turf.BoundingBox) -> Bool { guard let coordinates = shape?.coordinates else { return false } @@ -81,54 +68,19 @@ extension RouteStep { } return false } - - var initialCoordinate: CLLocationCoordinate2D? { - return shape?.coordinates.first - } - - var finalCoordinate: CLLocationCoordinate2D? { - return shape?.coordinates.last - } - - func beginsOn(_ line: LineString, tolerance: CLLocationDistance = 10) -> Bool { - - guard let initialCoordinate = initialCoordinate, let closestCoordinate = line.closestCoordinate(to: initialCoordinate) else { return false } - - - let distanceAtStart = closestCoordinate.coordinate.distance(to: initialCoordinate) - return distanceAtStart <= tolerance - } - - func endsOn(_ line: LineString, tolerance: CLLocationDistance = 10) -> Bool { - - guard let finalCoordinate = finalCoordinate, let closestCoordinate = line.closestCoordinate(to: finalCoordinate) else { return false } - - - let distanceAtStart = closestCoordinate.coordinate.distance(to: finalCoordinate) - return distanceAtStart <= tolerance - } -} - -extension CLLocationCoordinate2D: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(latitude) - hasher.combine(longitude) - } -} -extension RouteStep: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(shape?.coordinates) - } } extension Array where Element == RouteStep { + // Find the longest contiguous series of RouteSteps connected to the first one. + // + // tolerance: -- Maximum distance between the end of one RouteStep and the start of the next to still consider them connected. Defaults to 100 meters func continuousShape(tolerance: CLLocationDistance = 100) -> LineString? { guard count > 0 else { return nil } guard count > 1 else { return self[0].shape } var continuousLine = [CLLocationCoordinate2D]() for index in 0...count-2 { - if let currentStepFinalCoordinate = self[index].finalCoordinate, currentStepFinalCoordinate.distance(to: self[index+1].maneuverLocation) < tolerance, let coordinates = self[index].shape?.coordinates { + if let currentStepFinalCoordinate = self[index].shape?.coordinates.last, currentStepFinalCoordinate.distance(to: self[index+1].maneuverLocation) < tolerance, let coordinates = self[index].shape?.coordinates { continuousLine.append(contentsOf: coordinates) } } From d9b60fde1ed930177515e73670874dcf2b062b51 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Tue, 17 Nov 2020 20:16:51 -0800 Subject: [PATCH 07/48] Filter candidate polyline verticies for route annotations to only consider ones that are within the visible map bounds. --- Example/ViewController.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 7144f518c0e..7addb8e4702 100755 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -131,13 +131,13 @@ class ViewController: UIViewController { if let continuousLine = visibleAlternateSteps.continuousShape(), continuousLine.coordinates.count > 0 { coordinate = continuousLine.coordinates[0] - // We don't need a full resolution polyline in order to find our spot so simplify and complex shapes with many vertices + // We don't need a full resolution polyline in order to find our spot so simplify any complex shapes with many vertices let simplifiedLine = continuousLine.coordinates.count < 100 ? continuousLine : continuousLine.simplified - // find the vertex that is the furthest from the location of the selected route's annotation - let distanceSortedVertices = simplifiedLine.coordinates.sorted { - $0.distance(to: selectedRouteCoordinate) < $1.distance(to: selectedRouteCoordinate) - } + // find the on-screen vertex that is the furthest from the location of the selected route's annotation + let distanceSortedVertices = simplifiedLine.coordinates + .filter { return visibleBoundingBox.contains($0) } + .sorted { $0.distance(to: selectedRouteCoordinate) < $1.distance(to: selectedRouteCoordinate) } let furthestDistance = distanceSortedVertices.last?.distance(to: selectedRouteCoordinate) ?? 0 var furthestVertex = kCLLocationCoordinate2DInvalid @@ -165,7 +165,7 @@ class ViewController: UIViewController { point.coordinate = coordinate var tailPosition = selectedRouteTailPosition == .left ? RouteETAAnnotationTailPosition.right : RouteETAAnnotationTailPosition.left - // pick the orientation of the bubble stem based on how close to the edge of the screen it is + // pick the orientation of the bubble "stem" based on how close to the edge of the screen it is if tailPosition == .left && unprojectedCoordinate.x > mapView.bounds.width * 0.75 { tailPosition = .right } else if tailPosition == .right && unprojectedCoordinate.x < mapView.bounds.width * 0.25 { From 51cd23a791ad94b2e78468fa68baf1606f636446 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Wed, 18 Nov 2020 09:36:38 -0800 Subject: [PATCH 08/48] Filter the route coordinates being considered down to only ones that are onscreen. Remove an unneeded extension to MGLMapView. --- Example/ViewController.swift | 25 ++++++++++++++++++++----- Extensions/MGLMapView+Example.swift | 7 ------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 7addb8e4702..96ae75b4d0f 100755 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -90,26 +90,36 @@ class ViewController: UIViewController { guard waypoints.count > 0, let routes = routes, let mapView = mapView else { return } - let visibleCoordinateBounds = mapView.coordinateBoundsInset(CGSize(width: 40, height: 60)) - let visibleBoundingBox = BoundingBox(coordinateBounds: visibleCoordinateBounds) + let visibleBoundingBox = BoundingBox(coordinateBounds: mapView.visibleCoordinateBounds) let tollRoutes = routes.filter { route -> Bool in return (route.tollIntersections?.count ?? 0) > 0 } let routesContainTolls = tollRoutes.count > 0 + // Run through our heurstic algorithm looking for a good coordinate along each route line to place it's route annotation guard let selectedRoute = routes.first else { return } guard let visibleSelectedRoute = selectedRoute.shapes(within: visibleBoundingBox), let selectedRouteShape = visibleSelectedRoute.first else { return } - guard let selectedRouteCoordinate = selectedRouteShape.coordinateAtNormalizedPosition(Double.random(in: 0.10...0.3)) else { return } + // simplify the polyline of the selected route shape to reduce number of points considered + let selectedRouteLine = selectedRouteShape.coordinates.count < 100 ? selectedRouteShape : selectedRouteShape.simplified + + // filter to only vertices that are onscreen + let visibleRouteCoordinates = selectedRouteLine.coordinates.filter { + let unprojectedPoint = mapView.convert($0, toPointTo: nil) + return mapView.bounds.contains(unprojectedPoint) + } + + // pick a random vertex as our annotation coordinate + guard let selectedRouteCoordinate = visibleRouteCoordinates.randomElement() else { return } let selectedRouteTailPosition = mapView.convert(selectedRouteCoordinate, toPointTo: nil).x <= mapView.bounds.width / 2 ? RouteETAAnnotationTailPosition.left : RouteETAAnnotationTailPosition.right var features = [MGLPointFeature]() // we will look for a set of RouteSteps unique to each alternate route, then find a coordinate along that portion of the route line - // to use as the position of the annotation callout + // to use as the position of the annotation callout for that route var excludedSteps = selectedRoute.legs.compactMap { return $0.steps }.reduce([], +) for (index, route) in routes.dropFirst().enumerated() { let allSteps = route.legs.compactMap { return $0.steps }.reduce([], +) @@ -135,8 +145,12 @@ class ViewController: UIViewController { let simplifiedLine = continuousLine.coordinates.count < 100 ? continuousLine : continuousLine.simplified // find the on-screen vertex that is the furthest from the location of the selected route's annotation + // this will usually yield a coordinate that is visually far enough to not overlap let distanceSortedVertices = simplifiedLine.coordinates - .filter { return visibleBoundingBox.contains($0) } + .filter { + let unprojectedPoint = mapView.convert($0, toPointTo: nil) + return mapView.bounds.contains(unprojectedPoint) + } .sorted { $0.distance(to: selectedRouteCoordinate) < $1.distance(to: selectedRouteCoordinate) } let furthestDistance = distanceSortedVertices.last?.distance(to: selectedRouteCoordinate) ?? 0 @@ -155,6 +169,7 @@ class ViewController: UIViewController { } } + // form the appropriate text string for the annotation let labelText = annotationLabelForRoute(route, tolls: routesContainTolls) // convert our coordinate to screen space so we make some choices on which side of the coordinate the label ends up on diff --git a/Extensions/MGLMapView+Example.swift b/Extensions/MGLMapView+Example.swift index 9242e4275dc..d575d7b014c 100644 --- a/Extensions/MGLMapView+Example.swift +++ b/Extensions/MGLMapView+Example.swift @@ -2,13 +2,6 @@ import Foundation import Mapbox import Turf -extension MGLMapView { - func coordinateBoundsInset(_ inset: CGSize) -> MGLCoordinateBounds { - return convert(bounds.insetBy(dx: inset.width, dy: inset.height), toCoordinateBoundsFrom: nil) - } -} - - extension MGLStyle { func addDebugCircleLayer(identifier: String, coordinate: CLLocationCoordinate2D, color: UIColor = UIColor.purple) { From 86806e189e193cf4dc5a477cba9569956e8cfc33 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Wed, 18 Nov 2020 10:45:20 -0800 Subject: [PATCH 09/48] Remove more unnecessary code. Move the debug extensions for MGLStyle from the Example app to MapboxNavigation target. --- Extensions/MGLMapView+Example.swift | 104 ----------------- Extensions/Route+Example.swift | 5 - Extensions/Turf+Example.swift | 50 -------- MapboxNavigation.xcodeproj/project.pbxproj | 4 - Sources/MapboxNavigation/MGLStyle.swift | 126 +++++++++++++++++++++ 5 files changed, 126 insertions(+), 163 deletions(-) delete mode 100644 Extensions/MGLMapView+Example.swift diff --git a/Extensions/MGLMapView+Example.swift b/Extensions/MGLMapView+Example.swift deleted file mode 100644 index d575d7b014c..00000000000 --- a/Extensions/MGLMapView+Example.swift +++ /dev/null @@ -1,104 +0,0 @@ -import Foundation -import Mapbox -import Turf - -extension MGLStyle { - - func addDebugCircleLayer(identifier: String, coordinate: CLLocationCoordinate2D, color: UIColor = UIColor.purple) { - let point = MGLPointFeature() - point.coordinate = coordinate - - let dataSource = MGLShapeSource(identifier: "addDebugCircleLayer" + identifier, features: [point], options: nil) - addSource(dataSource) - - let circle = MGLCircleStyleLayer(identifier: "addDebugCircleLayer" + identifier, source: dataSource) - circle.circleRadius = NSExpression(forConstantValue: 10) - circle.circleOpacity = NSExpression(forConstantValue: 0.75) - circle.circleColor = NSExpression(forConstantValue: color) - circle.circleStrokeWidth = NSExpression(forConstantValue: NSNumber(4)) - circle.circleStrokeColor = NSExpression(forConstantValue: UIColor.white) - - addLayer(circle) - } - - func addDebugLineLayer(identifier: String, coordinates: [CLLocationCoordinate2D], color: UIColor = UIColor.purple) { -// removeDebugLineLayers() - - let lineString = LineString(coordinates) - let lineFeature = MGLPolylineFeature(lineString) - let shapeSource = MGLShapeSource(identifier: "addDebugLineLayer" + identifier, features: [lineFeature], options: nil) - addSource(shapeSource) - - let lineLayer = MGLLineStyleLayer(identifier: "addDebugLineLayer" + identifier, source: shapeSource) - lineLayer.lineColor = NSExpression(forConstantValue: color) - lineLayer.lineWidth = NSExpression(forConstantValue: 8) - lineLayer.lineCap = NSExpression(forConstantValue: "round") - addLayer(lineLayer) - } - - func addDebugPolygonLayer(identifier: String, coordinates: [CLLocationCoordinate2D], color: UIColor = UIColor.purple) { - removeDebugFillLayers() - - let fillFeature = MGLPolygonFeature(coordinates: coordinates, count: UInt(coordinates.count)) - let shapeSource = MGLShapeSource(identifier: "addDebugPolygonLayer" + identifier, features: [fillFeature], options: nil) - addSource(shapeSource) - - let fillLayer = MGLFillStyleLayer(identifier: "addDebugPolygonLayer" + identifier, source: shapeSource) - fillLayer.fillColor = NSExpression(forConstantValue: color) - fillLayer.fillOpacity = NSExpression(forConstantValue: NSNumber(0.25)) - fillLayer.fillOutlineColor = NSExpression(forConstantValue: color) - fillLayer.fillOpacity = NSExpression(forConstantValue: NSNumber(0.75)) - addLayer(fillLayer) - } - - func removeDebugLineLayers() { - // remove any old layers - for lineLayer in layers.filter({ layer -> Bool in - guard let layer = layer as? MGLLineStyleLayer else { return false } - return layer.identifier.contains("addDebugLineLayer") - }) { - removeLayer(lineLayer) - } - - // remove any old sources - for dataSource in sources.filter({ source -> Bool in - return source.identifier.contains("addDebugLineLayer") - }) { - removeSource(dataSource) - } - } - - func removeDebugFillLayers() { - // remove any old layers - for lineLayer in layers.filter({ layer -> Bool in - guard let layer = layer as? MGLFillStyleLayer else { return false } - return layer.identifier.contains("addDebugPolygonLayer") - }) { - removeLayer(lineLayer) - } - - // remove any old sources - for dataSource in sources.filter({ source -> Bool in - return source.identifier.contains("addDebugPolygonLayer") - }) { - removeSource(dataSource) - } - } - - func removeDebugCircleLayers() { - // remove any old layers - for lineLayer in layers.filter({ layer -> Bool in - guard let layer = layer as? MGLCircleStyleLayer else { return false } - return layer.identifier.contains("addDebugCircleLayer") - }) { - removeLayer(lineLayer) - } - - // remove any old sources - for dataSource in sources.filter({ source -> Bool in - return source.identifier.contains("addDebugCircleLayer") - }) { - removeSource(dataSource) - } - } -} diff --git a/Extensions/Route+Example.swift b/Extensions/Route+Example.swift index c37ca3ff162..c176aa1e831 100644 --- a/Extensions/Route+Example.swift +++ b/Extensions/Route+Example.swift @@ -15,11 +15,6 @@ extension Route { #endif } - func stepsIntersecting(boundingBox: Turf.BoundingBox) -> [RouteStep]? { - let steps = legs.compactMap { return $0.steps }.reduce([], +) - return steps.filter { return $0.intersects(boundingBox) } - } - // returns the list of line segments along the route that fall within given bounding box. Returns nil if there are none. Line segments are defined by the route shape coordinates that lay within the bounding box func shapes(within: Turf.BoundingBox) -> [LineString]? { guard let coordinates = shape?.coordinates else { return nil } diff --git a/Extensions/Turf+Example.swift b/Extensions/Turf+Example.swift index e5415056ce6..befcedbc7d0 100644 --- a/Extensions/Turf+Example.swift +++ b/Extensions/Turf+Example.swift @@ -9,56 +9,6 @@ extension Turf.BoundingBox { } extension LineString { - var midpoint: CLLocationCoordinate2D? { - if let distance = self.distance(), let midpoint = self.coordinateFromStart(distance: distance/2) { - return midpoint - } - - return nil - } - - func coordinateAtNormalizedPosition(_ position: Double) -> CLLocationCoordinate2D? { - if let distance = self.distance(), let point = self.coordinateFromStart(distance: distance * position) { - return point - } - - return nil - } - - func segmentsIntersecting(boundingBox: Turf.BoundingBox) -> [LineString]? { - var lines = [[CLLocationCoordinate2D]]() - var currentLine: [CLLocationCoordinate2D]? - for coordinate in coordinates { - // see if this coordinate lays within the bounds - if boundingBox.contains(coordinate) { - // if there is no current line segment then start one - if currentLine == nil { - currentLine = [CLLocationCoordinate2D]() - } - - // append the coordinate to the current line segment - currentLine?.append(coordinate) - } else { - // if there is a current line segment being built then finish it off and reset - if let currentLine = currentLine { - lines.append(currentLine) - } - currentLine = nil - } - } - - // append any outstanding final segment - if let currentLine = currentLine { - lines.append(currentLine) - } - currentLine = nil - - // return the segments as LineStrings - return lines.compactMap { coordinateList -> LineString? in - return LineString(coordinateList) - } - } - var simplified: LineString { let simplifiedCoordinates = Simplify.simplify(coordinates, tolerance: 0.001) return LineString(simplifiedCoordinates) diff --git a/MapboxNavigation.xcodeproj/project.pbxproj b/MapboxNavigation.xcodeproj/project.pbxproj index ee174c2c0ac..c4fd945dc7e 100644 --- a/MapboxNavigation.xcodeproj/project.pbxproj +++ b/MapboxNavigation.xcodeproj/project.pbxproj @@ -402,7 +402,6 @@ DADD82802161EC0300B8B47D /* UIViewAnimationOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADD827F2161EC0300B8B47D /* UIViewAnimationOptionsTests.swift */; }; DAE22A2921C9DEDA00CA269D /* MGLVectorTileSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE22A2821C9DEDA00CA269D /* MGLVectorTileSourceTests.swift */; }; DAFA92071F01735000A7FB09 /* DistanceFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 351BEC0B1E5BCC72006FE110 /* DistanceFormatter.swift */; }; - F4052062255A16DC00F3DD47 /* MGLMapView+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052061255A16DC00F3DD47 /* MGLMapView+Example.swift */; }; F405206A255A171800F3DD47 /* Turf+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052069255A171800F3DD47 /* Turf+Example.swift */; }; F4052084255A178C00F3DD47 /* Route+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052083255A178C00F3DD47 /* Route+Example.swift */; }; F405208C255A17D300F3DD47 /* Locale+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F405208B255A17D300F3DD47 /* Locale+Example.swift */; }; @@ -1053,7 +1052,6 @@ DAFEB36D2093A11F00A86A83 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; DAFEB36E2093A3E000A86A83 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; DAFEB36F2093A3EF00A86A83 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ko; path = Resources/ko.lproj/Localizable.stringsdict; sourceTree = ""; }; - F4052061255A16DC00F3DD47 /* MGLMapView+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MGLMapView+Example.swift"; sourceTree = ""; }; F4052069255A171800F3DD47 /* Turf+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Turf+Example.swift"; sourceTree = ""; }; F4052083255A178C00F3DD47 /* Route+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Route+Example.swift"; sourceTree = ""; }; F405208B255A17D300F3DD47 /* Locale+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+Example.swift"; sourceTree = ""; }; @@ -1873,7 +1871,6 @@ F40520A1255A183B00F3DD47 /* Extensions */ = { isa = PBXGroup; children = ( - F4052061255A16DC00F3DD47 /* MGLMapView+Example.swift */, F4052069255A171800F3DD47 /* Turf+Example.swift */, F4052083255A178C00F3DD47 /* Route+Example.swift */, F405208B255A17D300F3DD47 /* Locale+Example.swift */, @@ -2691,7 +2688,6 @@ F405208C255A17D300F3DD47 /* Locale+Example.swift in Sources */, 358D14661E5E3B7700ADE590 /* AppDelegate.swift in Sources */, 3529FCF621A5C5D900AEA9AA /* UIViewController.swift in Sources */, - F4052062255A16DC00F3DD47 /* MGLMapView+Example.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/MapboxNavigation/MGLStyle.swift b/Sources/MapboxNavigation/MGLStyle.swift index e983fd6a4e9..81ab69a5a35 100644 --- a/Sources/MapboxNavigation/MGLStyle.swift +++ b/Sources/MapboxNavigation/MGLStyle.swift @@ -1,5 +1,6 @@ import Foundation import Mapbox +import Turf extension MGLStyle { // The Mapbox China Day Style URL. @@ -87,4 +88,129 @@ extension MGLStyle { removeSource(source) } } + + /** + Convenience method for adding a circle at a given coordinate. + + Useful for debugging or visualizing data. + */ + public func addDebugCircleLayer(identifier: String, coordinate: CLLocationCoordinate2D, color: UIColor = UIColor.purple) { + let point = MGLPointFeature() + point.coordinate = coordinate + + let dataSource = MGLShapeSource(identifier: "debugCircleLayer" + identifier, features: [point], options: nil) + addSource(dataSource) + + let circle = MGLCircleStyleLayer(identifier: "debugCircleLayer" + identifier, source: dataSource) + circle.circleRadius = NSExpression(forConstantValue: 10) + circle.circleOpacity = NSExpression(forConstantValue: 0.75) + circle.circleColor = NSExpression(forConstantValue: color) + circle.circleStrokeWidth = NSExpression(forConstantValue: NSNumber(4)) + circle.circleStrokeColor = NSExpression(forConstantValue: UIColor.white) + + addLayer(circle) + } + + /** + Convenience method for adding a line connecting a given set of coordinates. + + Useful for debugging or visualizing data. + */ + public func addDebugLineLayer(identifier: String, coordinates: [CLLocationCoordinate2D], color: UIColor = UIColor.purple) { + let lineString = LineString(coordinates) + let lineFeature = MGLPolylineFeature(lineString) + let shapeSource = MGLShapeSource(identifier: "debugLineLayer" + identifier, features: [lineFeature], options: nil) + addSource(shapeSource) + + let lineLayer = MGLLineStyleLayer(identifier: "debugLineLayer" + identifier, source: shapeSource) + lineLayer.lineColor = NSExpression(forConstantValue: color) + lineLayer.lineWidth = NSExpression(forConstantValue: 8) + lineLayer.lineCap = NSExpression(forConstantValue: "round") + addLayer(lineLayer) + } + + /** + Convenience method for adding a polygon shape. + + Useful for debugging or visualizing data. + */ + public func addDebugPolygonLayer(identifier: String, coordinates: [CLLocationCoordinate2D], color: UIColor = UIColor.purple) { + removeDebugPolygonLayers() + + let fillFeature = MGLPolygonFeature(coordinates: coordinates, count: UInt(coordinates.count)) + let shapeSource = MGLShapeSource(identifier: "debugPolygonLayer" + identifier, features: [fillFeature], options: nil) + addSource(shapeSource) + + let fillLayer = MGLFillStyleLayer(identifier: "debugPolygonLayer" + identifier, source: shapeSource) + fillLayer.fillColor = NSExpression(forConstantValue: color) + fillLayer.fillOpacity = NSExpression(forConstantValue: NSNumber(0.25)) + fillLayer.fillOutlineColor = NSExpression(forConstantValue: color) + fillLayer.fillOpacity = NSExpression(forConstantValue: NSNumber(0.75)) + addLayer(fillLayer) + } + + /** + Method to remove any debug line style layers. + + Call to clean up when you no longer need any debug layers added with addDebugLineLayer(identifier:, coordinates:, color:) + */ + public func removeDebugLineLayers() { + // remove any old layers + let styleLayers = layers.filter({ layer -> Bool in + guard let layer = layer as? MGLLineStyleLayer else { return false } + return layer.identifier.contains("debugLineLayer") + }) + + remove(styleLayers) + + // remove any old sources + let dataSources = sources.filter({ source -> Bool in + return source.identifier.contains("debugLineLayer") + }) + + remove(dataSources) + } + + /** + Method to remove any debug fill style layers. + + Call to clean up when you no longer need any debug layers added with addDebugPolygonLayer(identifier:, coordinates:, color:) + */ + public func removeDebugPolygonLayers() { + let styleLayers = layers.filter({ layer -> Bool in + guard let layer = layer as? MGLFillStyleLayer else { return false } + return layer.identifier.contains("debugPolygonLayer") + }) + + remove(styleLayers) + + // remove any old sources + let dataSources = sources.filter({ source -> Bool in + return source.identifier.contains("debugPolygonLayer") + }) + + remove(dataSources) + } + + /** + Method to remove any debug circle style layers. + + Call to clean up when you no longer need any debug layers added with addDebugCircleLayer(identifier:, coordinate:, color:) + */ + public func removeDebugCircleLayers() { + // remove any old layers + let styleLayers = layers.filter({ layer -> Bool in + guard let layer = layer as? MGLCircleStyleLayer else { return false } + return layer.identifier.contains("debugCircleLayer") + }) + + remove(styleLayers) + + // remove any old sources + let dataSources = sources.filter({ source -> Bool in + return source.identifier.contains("debugCircleLayer") + }) + + remove(dataSources) + } } From 06f0e03f22b347cb2cc7c54ca7051821309a8d63 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Wed, 18 Nov 2020 13:40:57 -0800 Subject: [PATCH 10/48] Rename constant value. --- Example/ViewController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 96ae75b4d0f..14cb8b32e28 100755 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -7,7 +7,7 @@ import Turf private typealias RouteRequestSuccess = ((RouteResponse) -> Void) private typealias RouteRequestFailure = ((Error) -> Void) -private enum RouteETAAnnotationTailPosition: Int { +private enum RouteDurationAnnotationTailPosition: Int { case left case right } @@ -114,7 +114,7 @@ class ViewController: UIViewController { // pick a random vertex as our annotation coordinate guard let selectedRouteCoordinate = visibleRouteCoordinates.randomElement() else { return } - let selectedRouteTailPosition = mapView.convert(selectedRouteCoordinate, toPointTo: nil).x <= mapView.bounds.width / 2 ? RouteETAAnnotationTailPosition.left : RouteETAAnnotationTailPosition.right + let selectedRouteTailPosition = mapView.convert(selectedRouteCoordinate, toPointTo: nil).x <= mapView.bounds.width / 2 ? RouteDurationAnnotationTailPosition.left : RouteDurationAnnotationTailPosition.right var features = [MGLPointFeature]() @@ -178,7 +178,7 @@ class ViewController: UIViewController { // Create the feature for this route annotation. Set the styling attributes that will be used to render the annotation in the style layer. let point = MGLPointFeature() point.coordinate = coordinate - var tailPosition = selectedRouteTailPosition == .left ? RouteETAAnnotationTailPosition.right : RouteETAAnnotationTailPosition.left + var tailPosition = selectedRouteTailPosition == .left ? RouteDurationAnnotationTailPosition.right : RouteDurationAnnotationTailPosition.left // pick the orientation of the bubble "stem" based on how close to the edge of the screen it is if tailPosition == .left && unprojectedCoordinate.x > mapView.bounds.width * 0.75 { From 181c21cc3c94f4897a7a5566eba578ac42a6e5dd Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Tue, 24 Nov 2020 10:42:26 -0800 Subject: [PATCH 11/48] Move CarPlay DateComponentFormatter extensions to shared extension file in main Nav SDK. Use these extensions in Route Annotation Anotations and CPTrip. Fix new source files that were omitted from the Example-CarPlay application target. --- Example/ViewController.swift | 8 +----- MapboxNavigation.xcodeproj/project.pbxproj | 12 +++++++++ ...entsFormatter+MGLNavigationAdditions.swift | 24 +++++++++++++++++ Sources/MapboxNavigation/CPTrip.swift | 27 +++---------------- 4 files changed, 40 insertions(+), 31 deletions(-) create mode 100644 MapboxNavigation/DateComponentsFormatter+MGLNavigationAdditions.swift diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 14cb8b32e28..fc82f0b0b15 100755 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -61,8 +61,6 @@ class ViewController: UIViewController { } } - fileprivate let dateComponentsFormatter = DateComponentsFormatter() - private func updateAnnotationSymbolImages(_ style: MGLStyle) { let capInsetHeight = CGFloat(22) let capInsetWidth = CGFloat(11) @@ -270,7 +268,7 @@ class ViewController: UIViewController { // This function generates the text for the label to be shown on screen. It will include estimated duration and info on Tolls, if applicable private func annotationLabelForRoute(_ route: Route, tolls: Bool) -> String { - var eta = dateComponentsFormatter.string(from: route.expectedTravelTime) ?? "" + var eta = DateComponentsFormatter.shortDateComponentsFormatter.string(from: route.expectedTravelTime) ?? "" let hasTolls = (route.tollIntersections?.count ?? 0) > 0 if hasTolls { @@ -330,10 +328,6 @@ class ViewController: UIViewController { if let appDelegate = UIApplication.shared.delegate as? AppDelegate { appDelegate.currentAppRootViewController = self } - - dateComponentsFormatter.maximumUnitCount = 3 - dateComponentsFormatter.allowedUnits = [.hour, .minute] - dateComponentsFormatter.unitsStyle = .short } deinit { diff --git a/MapboxNavigation.xcodeproj/project.pbxproj b/MapboxNavigation.xcodeproj/project.pbxproj index c4fd945dc7e..8d924dd841e 100644 --- a/MapboxNavigation.xcodeproj/project.pbxproj +++ b/MapboxNavigation.xcodeproj/project.pbxproj @@ -410,6 +410,11 @@ F4A415F8255DEAD500373466 /* Simplify.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A415F6255DEAD500373466 /* Simplify.swift */; }; F4BF512E24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BF512D24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift */; }; F4C5A26F24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C5A26E24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift */; }; + F4FD7739256D886E007C525A /* DateComponentsFormatter+MGLNavigationAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FD7738256D886E007C525A /* DateComponentsFormatter+MGLNavigationAdditions.swift */; }; + F4FD7740256D89C6007C525A /* Turf+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052069255A171800F3DD47 /* Turf+Example.swift */; }; + F4FD7747256D89CA007C525A /* Route+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052083255A178C00F3DD47 /* Route+Example.swift */; }; + F4FD774E256D89CD007C525A /* Locale+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F405208B255A17D300F3DD47 /* Locale+Example.swift */; }; + F4FD7755256D89D0007C525A /* UIImage+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052099255A180500F3DD47 /* UIImage+Example.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1059,6 +1064,7 @@ F4A415F6255DEAD500373466 /* Simplify.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Simplify.swift; path = Example/Simplify.swift; sourceTree = ""; }; F4BF512D24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackSubtypeCollectionViewCell.swift; sourceTree = ""; }; F4C5A26E24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackSubtypeViewController.swift; sourceTree = ""; }; + F4FD7738256D886E007C525A /* DateComponentsFormatter+MGLNavigationAdditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateComponentsFormatter+MGLNavigationAdditions.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1702,6 +1708,7 @@ C5A7EC5B1FD610A80008B9BA /* VisualInstructionComponent.swift */, 350E2C5E22707EB80014CEB3 /* UIScreen.swift */, CFD47D8F20FD85EC00BC1E49 /* MGLAccountManager.swift */, + F4FD7738256D886E007C525A /* DateComponentsFormatter+MGLNavigationAdditions.swift */, ); name = Extensions; sourceTree = ""; @@ -2602,6 +2609,7 @@ DA8F3A7623B5D84900B56786 /* SpeedLimitView.swift in Sources */, 8AA849E924E722410008EE59 /* WaypointStyle.swift in Sources */, 359D1B281FFE70D30052FA42 /* NavigationView.swift in Sources */, + F4FD7739256D886E007C525A /* DateComponentsFormatter+MGLNavigationAdditions.swift in Sources */, C5FFAC1520D96F5C009E7F98 /* CarPlayNavigationViewController.swift in Sources */, 8DE879661FBB9980002F06C0 /* EndOfRouteViewController.swift in Sources */, AE47A33422B1F6AE0096458C /* InstructionsCardContainerView.swift in Sources */, @@ -2786,11 +2794,15 @@ C53F2EE420EBC95600D9798F /* ViewController.swift in Sources */, C53F2EE520EBC95600D9798F /* CustomViewController.swift in Sources */, DA303CAA21B7A93400F921DC /* ResizableView.swift in Sources */, + F4FD7740256D89C6007C525A /* Turf+Example.swift in Sources */, + F4FD7755256D89D0007C525A /* UIImage+Example.swift in Sources */, C53F2EE720EBC95600D9798F /* WaypointConfirmationViewController.swift in Sources */, C5DE4B6220F6B6B3007AFBE6 /* CustomStyles.swift in Sources */, + F4FD774E256D89CD007C525A /* Locale+Example.swift in Sources */, DA8805002316EAED00B54D87 /* ViewController+GuidanceCards.swift in Sources */, 35379CFD21480C0500FD402E /* AppDelegate+CarPlay.swift in Sources */, C53F2EE820EBC95600D9798F /* AppDelegate.swift in Sources */, + F4FD7747256D89CA007C525A /* Route+Example.swift in Sources */, DA303CA621B7A90100F921DC /* UIViewController.swift in Sources */, F4A415F8255DEAD500373466 /* Simplify.swift in Sources */, 3577B878214FF35800094294 /* FavoritesList.swift in Sources */, diff --git a/MapboxNavigation/DateComponentsFormatter+MGLNavigationAdditions.swift b/MapboxNavigation/DateComponentsFormatter+MGLNavigationAdditions.swift new file mode 100644 index 00000000000..9c66676ae4d --- /dev/null +++ b/MapboxNavigation/DateComponentsFormatter+MGLNavigationAdditions.swift @@ -0,0 +1,24 @@ +import Foundation + +extension DateComponentsFormatter { + public static let fullDateComponentsFormatter: DateComponentsFormatter = { + let formatter = DateComponentsFormatter() + formatter.unitsStyle = .full + formatter.allowedUnits = [.day, .hour, .minute] + return formatter + }() + + public static let shortDateComponentsFormatter: DateComponentsFormatter = { + let formatter = DateComponentsFormatter() + formatter.unitsStyle = .short + formatter.allowedUnits = [.day, .hour, .minute] + return formatter + }() + + public static let briefDateComponentsFormatter: DateComponentsFormatter = { + let formatter = DateComponentsFormatter() + formatter.unitsStyle = .brief + formatter.allowedUnits = [.day, .hour, .minute] + return formatter + }() +} diff --git a/Sources/MapboxNavigation/CPTrip.swift b/Sources/MapboxNavigation/CPTrip.swift index 6a4140cfada..3f92a18555e 100644 --- a/Sources/MapboxNavigation/CPTrip.swift +++ b/Sources/MapboxNavigation/CPTrip.swift @@ -5,33 +5,12 @@ import CarPlay @available(iOS 12.0, *) extension CPTrip { - static let fullDateComponentsFormatter: DateComponentsFormatter = { - let formatter = DateComponentsFormatter() - formatter.unitsStyle = .full - formatter.allowedUnits = [.day, .hour, .minute] - return formatter - }() - - static let shortDateComponentsFormatter: DateComponentsFormatter = { - let formatter = DateComponentsFormatter() - formatter.unitsStyle = .short - formatter.allowedUnits = [.day, .hour, .minute] - return formatter - }() - - static let briefDateComponentsFormatter: DateComponentsFormatter = { - let formatter = DateComponentsFormatter() - formatter.unitsStyle = .brief - formatter.allowedUnits = [.day, .hour, .minute] - return formatter - }() - convenience init(routes: [Route], routeOptions: RouteOptions, waypoints: [Waypoint]) { let routeChoices = routes.enumerated().map { (routeIndex, route) -> CPRouteChoice in let summaryVariants = [ - CPTrip.fullDateComponentsFormatter.string(from: route.expectedTravelTime)!, - CPTrip.shortDateComponentsFormatter.string(from: route.expectedTravelTime)!, - CPTrip.briefDateComponentsFormatter.string(from: route.expectedTravelTime)! + DateComponentsFormatter.fullDateComponentsFormatter.string(from: route.expectedTravelTime)!, + DateComponentsFormatter.shortDateComponentsFormatter.string(from: route.expectedTravelTime)!, + DateComponentsFormatter.briefDateComponentsFormatter.string(from: route.expectedTravelTime)! ] let routeChoice = CPRouteChoice(summaryVariants: summaryVariants, additionalInformationVariants: [route.description], From b88e8b5b7ecd6e32763cac5bf472c351a46f0b2f Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Tue, 24 Nov 2020 10:43:34 -0800 Subject: [PATCH 12/48] Rename layer identifier constant value. --- Example/ViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/ViewController.swift b/Example/ViewController.swift index fc82f0b0b15..cde3cf47836 100755 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -203,7 +203,7 @@ class ViewController: UIViewController { addRouteAnnotationSymbolLayer(features: features, style: style) } - private let annotationLayerIdentifier = "RouteETAAnnotations" + private let annotationLayerIdentifier = "RouteDurationAnnotations" private func addRouteAnnotationSymbolLayer(features: [MGLPointFeature], style: MGLStyle) { let dataSource: MGLShapeSource From 9b2eda0057958132612299c020afc264ad1cb80b Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Mon, 7 Dec 2020 20:12:37 -0800 Subject: [PATCH 13/48] Udpate to latest Turf with support for Polyline simplification. --- Example/Simplify.swift | 201 --------------------- Example/ViewController.swift | 8 +- Extensions/Turf+Example.swift | 7 - MapboxNavigation.xcodeproj/project.pbxproj | 6 - 4 files changed, 4 insertions(+), 218 deletions(-) delete mode 100644 Example/Simplify.swift diff --git a/Example/Simplify.swift b/Example/Simplify.swift deleted file mode 100644 index 5fb4b3ec850..00000000000 --- a/Example/Simplify.swift +++ /dev/null @@ -1,201 +0,0 @@ -// -// Simplify.swift -// -// Copyright (c) 2018 Tomislav Filipcic -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import Foundation -import CoreLocation -import CoreGraphics - -public protocol SimplifyValue { - var xValue: Double { get } - var yValue: Double { get } -} - -func equalsPoints(l: T, r: T) -> Bool { - return l.xValue == r.xValue && l.yValue == r.yValue -} - -extension CGPoint: SimplifyValue { - - public var xValue: Double { - return Double(x) - } - - public var yValue: Double { - return Double(y) - } -} - -extension CLLocationCoordinate2D: SimplifyValue { - - public var xValue: Double { - return latitude - } - - public var yValue: Double { - return longitude - } -} - -open class Simplify { - /** - Calculate square distance - - - parameter pointA: from point - - parameter pointB: to point - - - returns: square distance between two points - */ - fileprivate class func getSquareDistance(_ pointA: T,_ pointB: T) -> Float { - return Float((pointA.xValue - pointB.xValue) * (pointA.xValue - pointB.xValue) + (pointA.yValue - pointB.yValue) * (pointA.yValue - pointB.yValue)) - } - - /** - Calculate square distance from a point to a segment - - - parameter point: from point - - parameter seg1: segment first point - - parameter seg2: segment last point - - - returns: square distance between point to a segment - */ - fileprivate class func getSquareSegmentDistance(point p: T, seg1 s1: T, seg2 s2: T) -> Float { - - var x = s1.xValue - var y = s1.yValue - var dx = s2.xValue - x - var dy = s2.yValue - y - - if dx != 0 || dy != 0 { - let t = ((p.xValue - x) * dx + (p.yValue - y) * dy) / ((dx * dx) + (dy * dy)) - if t > 1 { - x = s2.xValue - y = s2.yValue - } else if t > 0 { - x += dx * t - y += dy * t - } - } - - dx = p.xValue - x - dy = p.yValue - y - - return Float((dx * dx) + (dy * dy)) - } - - /** - Simplify an array of points using the Ramer-Douglas-Peucker algorithm - - - parameter points: An array of points - - parameter tolerance: Affects the amount of simplification (in the same metric as the point coordinates) - - - returns: Returns an array of simplified points - */ - fileprivate class func simplifyDouglasPeucker(_ points: [T], tolerance: Float!) -> [T] { - if points.count <= 2 { - return points - } - - let lastPoint: Int = points.count - 1 - var result: [T] = [points.first!] - simplifyDouglasPeuckerStep(points, first: 0, last: lastPoint, tolerance: tolerance, simplified: &result) - result.append(points[lastPoint]) - return result - } - - fileprivate class func simplifyDouglasPeuckerStep(_ points: [T], first: Int, last: Int, tolerance: Float, simplified: inout [T]) { - var maxSquareDistance = tolerance - var index = 0 - - for i in first + 1 ..< last { - let sqDist = getSquareSegmentDistance(point: points[i], seg1: points[first], seg2: points[last]) - if sqDist > maxSquareDistance { - index = i - maxSquareDistance = sqDist - } - } - - if maxSquareDistance > tolerance { - if index - first > 1 { - simplifyDouglasPeuckerStep(points, first: first, last: index, tolerance: tolerance, simplified: &simplified) - } - simplified.append(points[index]) - if last - index > 1 { - simplifyDouglasPeuckerStep(points, first: index, last: last, tolerance: tolerance, simplified: &simplified) - } - } - } - - /** - Simplify an array of points using the Radial Distance algorithm - - - parameter points: An array of points - - parameter tolerance: Affects the amount of simplification (in the same metric as the point coordinates) - - - returns: Returns an array of simplified points - */ - fileprivate class func simplifyRadialDistance(_ points: [T], tolerance: Float!) -> [T] { - if points.count <= 2 { - return points - } - - var prevPoint: T = points.first! - var newPoints: [T] = [prevPoint] - var point: T = points[1] - - for idx in 1 ..< points.count { - point = points[idx] - let distance = getSquareDistance(point, prevPoint) - if distance > tolerance! { - newPoints.append(point) - prevPoint = point - } - } - - if !equalsPoints(l: prevPoint, r: point) { - newPoints.append(point) - } - - return newPoints - } - - /** - Returns an array of simplified points - - - parameter points: An array of points - - parameter tolerance: Affects the amount of simplification (in the same metric as the point coordinates) - - parameter highQuality: Excludes distance-based preprocessing step which leads to highest quality simplification but runs ~10-20 times slower - - - returns: Returns an array of simplified points - */ - - open class func simplify(_ points: [T], tolerance: Float?, highQuality: Bool = false) -> [T] { - if points.count <= 2 { - return points - } - - let squareTolerance = (tolerance != nil ? tolerance! * tolerance! : 1.0) - var result: [T] = (highQuality == true ? points : simplifyRadialDistance(points, tolerance: squareTolerance)) - result = simplifyDouglasPeucker(result, tolerance: squareTolerance) - return result - } -} diff --git a/Example/ViewController.swift b/Example/ViewController.swift index cde3cf47836..bc1fbeb332c 100755 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -98,11 +98,11 @@ class ViewController: UIViewController { // Run through our heurstic algorithm looking for a good coordinate along each route line to place it's route annotation guard let selectedRoute = routes.first else { return } - guard let visibleSelectedRoute = selectedRoute.shapes(within: visibleBoundingBox), let selectedRouteShape = visibleSelectedRoute.first else { return } + guard let visibleSelectedRoute = selectedRoute.shapes(within: visibleBoundingBox), var selectedRouteShape = visibleSelectedRoute.first else { return } + selectedRouteShape.simplified(tolerance: 0.001, highestQuality: false) // simplify the polyline of the selected route shape to reduce number of points considered - let selectedRouteLine = selectedRouteShape.coordinates.count < 100 ? selectedRouteShape : selectedRouteShape.simplified - + let selectedRouteLine = selectedRouteShape.coordinates.count < 100 ? selectedRouteShape : selectedRouteShape // filter to only vertices that are onscreen let visibleRouteCoordinates = selectedRouteLine.coordinates.filter { let unprojectedPoint = mapView.convert($0, toPointTo: nil) @@ -140,7 +140,7 @@ class ViewController: UIViewController { coordinate = continuousLine.coordinates[0] // We don't need a full resolution polyline in order to find our spot so simplify any complex shapes with many vertices - let simplifiedLine = continuousLine.coordinates.count < 100 ? continuousLine : continuousLine.simplified + let simplifiedLine = continuousLine.coordinates.count < 100 ? continuousLine : continuousLine.simplify(tolerance: 0.001, highestQuality: false) // find the on-screen vertex that is the furthest from the location of the selected route's annotation // this will usually yield a coordinate that is visually far enough to not overlap diff --git a/Extensions/Turf+Example.swift b/Extensions/Turf+Example.swift index befcedbc7d0..ffbe8d93bc9 100644 --- a/Extensions/Turf+Example.swift +++ b/Extensions/Turf+Example.swift @@ -7,10 +7,3 @@ extension Turf.BoundingBox { self.init(coordinateBounds.sw, coordinateBounds.ne) } } - -extension LineString { - var simplified: LineString { - let simplifiedCoordinates = Simplify.simplify(coordinates, tolerance: 0.001) - return LineString(simplifiedCoordinates) - } -} diff --git a/MapboxNavigation.xcodeproj/project.pbxproj b/MapboxNavigation.xcodeproj/project.pbxproj index 8d924dd841e..7b69f08ed9c 100644 --- a/MapboxNavigation.xcodeproj/project.pbxproj +++ b/MapboxNavigation.xcodeproj/project.pbxproj @@ -406,8 +406,6 @@ F4052084255A178C00F3DD47 /* Route+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052083255A178C00F3DD47 /* Route+Example.swift */; }; F405208C255A17D300F3DD47 /* Locale+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F405208B255A17D300F3DD47 /* Locale+Example.swift */; }; F405209A255A180500F3DD47 /* UIImage+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052099255A180500F3DD47 /* UIImage+Example.swift */; }; - F4A415F7255DEAD500373466 /* Simplify.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A415F6255DEAD500373466 /* Simplify.swift */; }; - F4A415F8255DEAD500373466 /* Simplify.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A415F6255DEAD500373466 /* Simplify.swift */; }; F4BF512E24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BF512D24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift */; }; F4C5A26F24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C5A26E24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift */; }; F4FD7739256D886E007C525A /* DateComponentsFormatter+MGLNavigationAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FD7738256D886E007C525A /* DateComponentsFormatter+MGLNavigationAdditions.swift */; }; @@ -1061,7 +1059,6 @@ F4052083255A178C00F3DD47 /* Route+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Route+Example.swift"; sourceTree = ""; }; F405208B255A17D300F3DD47 /* Locale+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+Example.swift"; sourceTree = ""; }; F4052099255A180500F3DD47 /* UIImage+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Example.swift"; sourceTree = ""; }; - F4A415F6255DEAD500373466 /* Simplify.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Simplify.swift; path = Example/Simplify.swift; sourceTree = ""; }; F4BF512D24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackSubtypeCollectionViewCell.swift; sourceTree = ""; }; F4C5A26E24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackSubtypeViewController.swift; sourceTree = ""; }; F4FD7738256D886E007C525A /* DateComponentsFormatter+MGLNavigationAdditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateComponentsFormatter+MGLNavigationAdditions.swift"; sourceTree = ""; }; @@ -1426,7 +1423,6 @@ 35B711DB1E5E7B70001EDA8D /* Example */ = { isa = PBXGroup; children = ( - F4A415F6255DEAD500373466 /* Simplify.swift */, 35002D721E5F6C830090E733 /* Supporting files */, 358D14651E5E3B7700ADE590 /* AppDelegate.swift */, 35379CFB21480BFB00FD402E /* AppDelegate+CarPlay.swift */, @@ -2687,7 +2683,6 @@ F4052084255A178C00F3DD47 /* Route+Example.swift in Sources */, 3529FCF221A5C5B400AEA9AA /* ResizableView.swift in Sources */, C5D9800D1EFA8BA9006DBF2E /* CustomViewController.swift in Sources */, - F4A415F7255DEAD500373466 /* Simplify.swift in Sources */, F405206A255A171800F3DD47 /* Turf+Example.swift in Sources */, F405209A255A180500F3DD47 /* UIImage+Example.swift in Sources */, AED6285622CBE4CE00058A51 /* ViewController+GuidanceCards.swift in Sources */, @@ -2804,7 +2799,6 @@ C53F2EE820EBC95600D9798F /* AppDelegate.swift in Sources */, F4FD7747256D89CA007C525A /* Route+Example.swift in Sources */, DA303CA621B7A90100F921DC /* UIViewController.swift in Sources */, - F4A415F8255DEAD500373466 /* Simplify.swift in Sources */, 3577B878214FF35800094294 /* FavoritesList.swift in Sources */, DA303CA921B7A93100F921DC /* SettingsItems.swift in Sources */, DA303CA721B7A91C00F921DC /* SettingsViewController.swift in Sources */, From b50879849a38240a71f86ed85816ab960e90229f Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Thu, 17 Dec 2020 11:29:11 -0800 Subject: [PATCH 14/48] Rework the route annotation algorithm to be simpler and more often visually appealing. --- Example/ViewController.swift | 258 +++++++++++++++++------------------ 1 file changed, 123 insertions(+), 135 deletions(-) diff --git a/Example/ViewController.swift b/Example/ViewController.swift index bc1fbeb332c..1a7a63f9926 100755 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -20,10 +20,10 @@ class ViewController: UIViewController { @IBOutlet weak var bottomBar: UIView! @IBOutlet weak var clearMap: UIButton! @IBOutlet weak var bottomBarBackground: UIView! - + var trackPolyline: MGLPolyline? var rawTrackPolyline: MGLPolyline? - + // MARK: Properties var mapView: NavigationMapView? { didSet { @@ -50,7 +50,7 @@ class ViewController: UIViewController { clearMapView() return } - + startButton.isEnabled = true mapView?.show(routes) mapView?.showWaypoints(on: currentRoute) @@ -86,7 +86,7 @@ class ViewController: UIViewController { // remove any existing route annotation removeRouteAnnotationsLayerFromStyle(style) - guard waypoints.count > 0, let routes = routes, let mapView = mapView else { return } + guard self.waypoints.count > 0, let routes = routes, let mapView = self.mapView else { return } let visibleBoundingBox = BoundingBox(coordinateBounds: mapView.visibleCoordinateBounds) @@ -95,31 +95,15 @@ class ViewController: UIViewController { } let routesContainTolls = tollRoutes.count > 0 - // Run through our heurstic algorithm looking for a good coordinate along each route line to place it's route annotation - guard let selectedRoute = routes.first else { return } - - guard let visibleSelectedRoute = selectedRoute.shapes(within: visibleBoundingBox), var selectedRouteShape = visibleSelectedRoute.first else { return } - - selectedRouteShape.simplified(tolerance: 0.001, highestQuality: false) - // simplify the polyline of the selected route shape to reduce number of points considered - let selectedRouteLine = selectedRouteShape.coordinates.count < 100 ? selectedRouteShape : selectedRouteShape - // filter to only vertices that are onscreen - let visibleRouteCoordinates = selectedRouteLine.coordinates.filter { - let unprojectedPoint = mapView.convert($0, toPointTo: nil) - return mapView.bounds.contains(unprojectedPoint) - } - - // pick a random vertex as our annotation coordinate - guard let selectedRouteCoordinate = visibleRouteCoordinates.randomElement() else { return } - - let selectedRouteTailPosition = mapView.convert(selectedRouteCoordinate, toPointTo: nil).x <= mapView.bounds.width / 2 ? RouteDurationAnnotationTailPosition.left : RouteDurationAnnotationTailPosition.right + // pick a random tail direction to keep things varied + guard let randomTailPosition = [RouteDurationAnnotationTailPosition.left, RouteDurationAnnotationTailPosition.right].randomElement() else { return } var features = [MGLPointFeature]() - // we will look for a set of RouteSteps unique to each alternate route, then find a coordinate along that portion of the route line - // to use as the position of the annotation callout for that route - var excludedSteps = selectedRoute.legs.compactMap { return $0.steps }.reduce([], +) - for (index, route) in routes.dropFirst().enumerated() { + // Run through our heuristic algorithm looking for a good coordinate along each route line to place it's route annotation + // First, we will look for a set of RouteSteps unique to each route + var excludedSteps = [RouteStep]() + for (index, route) in routes.enumerated() { let allSteps = route.legs.compactMap { return $0.steps }.reduce([], +) let alternateSteps = allSteps.filter { step -> Bool in for existingStep in excludedSteps { @@ -135,48 +119,53 @@ class ViewController: UIViewController { var coordinate = kCLLocationCoordinate2DInvalid - // Obtain a polyline of the set of steps. We'll look for a good spot along this line to place the annotation + // Obtain a polyline of the set of steps. We'll look for a good spot along this line to place the annotation. + // We will consider a good spot to be somewhere near the middle of the line, making sure that the coordinate is visible on-screen. if let continuousLine = visibleAlternateSteps.continuousShape(), continuousLine.coordinates.count > 0 { coordinate = continuousLine.coordinates[0] - // We don't need a full resolution polyline in order to find our spot so simplify any complex shapes with many vertices - let simplifiedLine = continuousLine.coordinates.count < 100 ? continuousLine : continuousLine.simplify(tolerance: 0.001, highestQuality: false) - - // find the on-screen vertex that is the furthest from the location of the selected route's annotation - // this will usually yield a coordinate that is visually far enough to not overlap - let distanceSortedVertices = simplifiedLine.coordinates - .filter { - let unprojectedPoint = mapView.convert($0, toPointTo: nil) - return mapView.bounds.contains(unprojectedPoint) + // Pick a coordinate using some randomness in order to give visual variety. + // Take care to snap that coordinate to one that lays on the original route line. + // If the chosen snapped coordinate is not visible onthe screen, then we walk back along the route coordinates looking for one that is. + // If none of the earlier points are on screen then we walk forward along the route coordinates until we find one that is. + if let distance = continuousLine.distance(), let sampleCoordinate = continuousLine.indexedCoordinateFromStart(distance: distance * CLLocationDistance.random(in: 0.3...0.8))?.coordinate, let routeShape = route.shape, let snappedCoordinate = routeShape.closestCoordinate(to: sampleCoordinate) { + var foundOnscreenCoordinate = false + var firstOnscreenCoordinate = snappedCoordinate.coordinate + for indexedCoordinate in routeShape.coordinates.prefix(through: snappedCoordinate.index).reversed() { + if visibleBoundingBox.contains(indexedCoordinate) { + firstOnscreenCoordinate = indexedCoordinate + foundOnscreenCoordinate = true + break + } } - .sorted { $0.distance(to: selectedRouteCoordinate) < $1.distance(to: selectedRouteCoordinate) } - - let furthestDistance = distanceSortedVertices.last?.distance(to: selectedRouteCoordinate) ?? 0 - var furthestVertex = kCLLocationCoordinate2DInvalid - // look for a vertex that is "far enough" from the selected annotation coordinate. We do this so we don't always put the annotations at the end of the route line. - for vertex in distanceSortedVertices { - if vertex.distance(to: selectedRouteCoordinate) >= furthestDistance * 0.75 { - furthestVertex = vertex - break + if foundOnscreenCoordinate { + // We found a point that is both on the route and on-screen + coordinate = firstOnscreenCoordinate + } else { + // we didn't find a previous point that is on-screen so we'll move forward through the coordinates looking for one + for indexedCoordinate in routeShape.coordinates.suffix(from: snappedCoordinate.index) { + if visibleBoundingBox.contains(indexedCoordinate) { + firstOnscreenCoordinate = indexedCoordinate + break + } + } + coordinate = firstOnscreenCoordinate } } - - if furthestVertex != kCLLocationCoordinate2DInvalid { - coordinate = continuousLine.closestCoordinate(to: furthestVertex)?.coordinate ?? furthestVertex - } } // form the appropriate text string for the annotation - let labelText = annotationLabelForRoute(route, tolls: routesContainTolls) - - // convert our coordinate to screen space so we make some choices on which side of the coordinate the label ends up on - let unprojectedCoordinate = mapView.convert(coordinate, toPointTo: nil) + let labelText = self.annotationLabelForRoute(route, tolls: routesContainTolls) // Create the feature for this route annotation. Set the styling attributes that will be used to render the annotation in the style layer. let point = MGLPointFeature() point.coordinate = coordinate - var tailPosition = selectedRouteTailPosition == .left ? RouteDurationAnnotationTailPosition.right : RouteDurationAnnotationTailPosition.left + + var tailPosition = randomTailPosition + + // convert our coordinate to screen space so we can make a choice on which side of the coordinate the label ends up on + let unprojectedCoordinate = mapView.convert(coordinate, toPointTo: nil) // pick the orientation of the bubble "stem" based on how close to the edge of the screen it is if tailPosition == .left && unprojectedCoordinate.x > mapView.bounds.width * 0.75 { @@ -185,22 +174,21 @@ class ViewController: UIViewController { tailPosition = .left } - let imageName = tailPosition == .left ? "RouteInfoAnnotationLeftHanded" : "RouteInfoAnnotationRightHanded" - point.attributes = ["selected": false, "tailPosition": tailPosition.rawValue, "text": labelText, "imageName": imageName, "sortOrder": -index] + var imageName = tailPosition == .left ? "RouteInfoAnnotationLeftHanded" : "RouteInfoAnnotationRightHanded" - features.append(point) - } + // the selected route uses the colored annotation image + if index == 0 { + imageName += "-Selected" + } - // add the annotation for the selected annotation last so it ends up ordered on top of the others - let labelText = annotationLabelForRoute(selectedRoute, tolls: routesContainTolls) + // set the feature attributes which will be used in styling the symbol style layer + point.attributes = ["selected": index == 0, "tailPosition": tailPosition.rawValue, "text": labelText, "imageName": imageName, "sortOrder": -index] - let point = MGLPointFeature() - point.coordinate = selectedRouteCoordinate - point.attributes = ["selected": true, "tailPosition": selectedRouteTailPosition.rawValue, "text": labelText, "imageName": selectedRouteTailPosition == .left ? "RouteInfoAnnotationLeftHanded-Selected" : "RouteInfoAnnotationRightHanded-Selected"] - features.append(point) + features.append(point) + } // add the features to the style - addRouteAnnotationSymbolLayer(features: features, style: style) + self.addRouteAnnotationSymbolLayer(features: features, style: style) } private let annotationLayerIdentifier = "RouteDurationAnnotations" @@ -284,7 +272,7 @@ class ViewController: UIViewController { return eta } - + weak var activeNavigationViewController: NavigationViewController? // MARK: Directions Request Handlers @@ -293,13 +281,13 @@ class ViewController: UIViewController { guard let routes = response.routes, !routes.isEmpty, case let .route(options) = response.options else { return } self?.mapView?.removeWaypoints() self?.response = response - + // Waypoints which were placed by the user are rewritten by slightly changed waypoints // which are returned in response with routes. if let waypoints = response.waypoints { self?.waypoints = waypoints } - + self?.clearMap.isHidden = false self?.longPressHintView.isHidden = true } @@ -309,38 +297,38 @@ class ViewController: UIViewController { print(error.localizedDescription) self?.presentAlert(message: error.localizedDescription) } - + private var foundAllBuildings = false // MARK: - Init - + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) commonInit() } - + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) commonInit() } - + private func commonInit() { if let appDelegate = UIApplication.shared.delegate as? AppDelegate { appDelegate.currentAppRootViewController = self } } - + deinit { if let mapView = mapView { uninstall(mapView) } } - + // MARK: - Lifecycle Methods override func viewDidLoad() { super.viewDidLoad() - + navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "settings"), style: .plain, target: self, action: #selector(openSettings)) navigationItem.rightBarButtonItem?.isEnabled = SettingsViewController.numberOfSections > 0 @@ -348,11 +336,11 @@ class ViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - + if mapView == nil { mapView = NavigationMapView(frame: view.bounds) } - + // Reset the navigation styling to the defaults if we are returning from a presentation. if (presentedViewController != nil) { DayStyle().apply() @@ -368,7 +356,7 @@ class ViewController: UIViewController { } } } - + @IBAction func openSettings() { let controller = UINavigationController(rootViewController: SettingsViewController()) present(controller, animated: true, completion: nil) @@ -386,14 +374,14 @@ class ViewController: UIViewController { if waypoints.count > 1 { waypoints = Array(waypoints.dropFirst()) } - + let destinationCoord = mapView.convert(tap.location(in: mapView), toCoordinateFrom: mapView) // Note: The destination name can be modified. The value is used in the top banner when arriving at a destination. let waypoint = Waypoint(coordinate: destinationCoord, name: "Dropped Pin #\(waypoints.endIndex + 1)") // Example of building highlighting. `targetCoordinate`, in this example, is used implicitly by NavigationViewController to determine which buildings to highlight. waypoint.targetCoordinate = destinationCoord waypoints.append(waypoint) - + // Example of highlighting buildings in 2d and directly using the API on NavigationMapView. let buildingHighlightCoordinates = waypoints.compactMap { $0.targetCoordinate } foundAllBuildings = mapView.highlightBuildings(at: buildingHighlightCoordinates, in3D: false) @@ -414,7 +402,7 @@ class ViewController: UIViewController { @IBAction func startButtonPressed(_ sender: Any) { presentActionsAlertController() } - + private func clearMapView() { startButton.isEnabled = false clearMap.isHidden = true @@ -425,25 +413,25 @@ class ViewController: UIViewController { style.removeDebugLineLayers() style.removeDebugCircleLayers() } - + mapView?.unhighlightBuildings() mapView?.removeRoutes() mapView?.removeWaypoints() waypoints.removeAll() } - + private func presentActionsAlertController() { let alertController = UIAlertController(title: "Start Navigation", message: "Select the navigation type", preferredStyle: .actionSheet) - + typealias ActionHandler = (UIAlertAction) -> Void - + let basic: ActionHandler = { _ in self.startBasicNavigation() } let day: ActionHandler = { _ in self.startNavigation(styles: [DayStyle()]) } let night: ActionHandler = { _ in self.startNavigation(styles: [NightStyle()]) } let custom: ActionHandler = { _ in self.startCustomNavigation() } let styled: ActionHandler = { _ in self.startStyledNavigation() } let guidanceCards: ActionHandler = { _ in self.startGuidanceCardsNavigation() } - + let actionPayloads: [(String, UIAlertAction.Style, ActionHandler?)] = [ ("Default UI", .default, basic), ("DayStyle UI", .default, day), @@ -453,16 +441,16 @@ class ViewController: UIViewController { ("Styled UI", .default, styled), ("Cancel", .cancel, nil) ] - + actionPayloads .map { payload in UIAlertAction(title: payload.0, style: payload.1, handler: payload.2) } .forEach(alertController.addAction(_:)) - + if let popoverController = alertController.popoverPresentationController { popoverController.sourceView = self.startButton popoverController.sourceRect = self.startButton.bounds } - + present(alertController, animated: true, completion: nil) } @@ -475,12 +463,12 @@ class ViewController: UIViewController { print("User location is not valid. Make sure to enable Location Services.") return } - + let userWaypoint = Waypoint(location: userLocation, heading: mapView.userLocation?.heading, name: "User location") waypoints.insert(userWaypoint, at: 0) let options = NavigationRouteOptions(waypoints: waypoints) - + // Get periodic updates regarding changes in estimated arrival time and traffic congestion segments along the route line. RouteControllerProactiveReroutingInterval = 30 @@ -502,44 +490,44 @@ class ViewController: UIViewController { func startBasicNavigation() { guard let response = response, let route = response.routes?.first, case let .route(routeOptions) = response.options else { return } - + let service = navigationService(route: route, routeIndex: 0, options: routeOptions) let navigationViewController = self.navigationViewController(navigationService: service) - + // Render part of the route that has been traversed with full transparency, to give the illusion of a disappearing route. navigationViewController.routeLineTracksTraversal = true - + // Example of building highlighting in 3D. navigationViewController.waypointStyle = .extrudedBuilding // Show second level of detail for feedback items. navigationViewController.detailedFeedbackEnabled = true - + presentAndRemoveMapview(navigationViewController, completion: beginCarPlayNavigation) } - + func startNavigation(styles: [Style]) { guard let response = response, let route = response.routes?.first, case let .route(routeOptions) = response.options else { return } - + let options = NavigationOptions(styles: styles, navigationService: navigationService(route: route, routeIndex: 0, options: routeOptions)) let navigationViewController = NavigationViewController(for: route, routeIndex: 0, routeOptions: routeOptions, navigationOptions: options) navigationViewController.delegate = self - + // Example of building highlighting in 2D. navigationViewController.waypointStyle = .building - + presentAndRemoveMapview(navigationViewController, completion: beginCarPlayNavigation) } - + func navigationViewController(navigationService: NavigationService) -> NavigationViewController { let options = NavigationOptions(navigationService: navigationService) - + let navigationViewController = NavigationViewController(for: navigationService.route, routeIndex: navigationService.indexedRoute.1, routeOptions: navigationService.routeProgress.routeOptions, navigationOptions: options) navigationViewController.delegate = self navigationViewController.mapView?.delegate = self return navigationViewController } - + public func beginNavigationWithCarplay(navigationService: NavigationService) { let navigationViewController = activeNavigationViewController ?? self.navigationViewController(navigationService: navigationService) navigationViewController.didConnectToCarPlay() @@ -548,7 +536,7 @@ class ViewController: UIViewController { presentAndRemoveMapview(navigationViewController, completion: nil) } - + // MARK: Custom Navigation UI func startCustomNavigation() { guard let route = response?.routes?.first, let responseOptions = response?.options, case let .route(routeOptions) = responseOptions else { return } @@ -578,21 +566,21 @@ class ViewController: UIViewController { presentAndRemoveMapview(navigationViewController, completion: beginCarPlayNavigation) } - + // MARK: Guidance Cards func startGuidanceCardsNavigation() { guard let response = response, let route = response.routes?.first, case let .route(routeOptions) = response.options else { return } - + let instructionsCardCollection = InstructionsCardViewController() instructionsCardCollection.cardCollectionDelegate = self - + let options = NavigationOptions(navigationService: navigationService(route: route, routeIndex: 0, options: routeOptions), topBanner: instructionsCardCollection) let navigationViewController = NavigationViewController(for: route, routeIndex: 0, routeOptions: routeOptions, navigationOptions: options) navigationViewController.delegate = self - + presentAndRemoveMapview(navigationViewController, completion: beginCarPlayNavigation) } - + func navigationService(route: Route, routeIndex: Int, options: RouteOptions) -> NavigationService { let simulate = simulationButton.isSelected let mode: SimulationMode = simulate ? .always : .onPoorGPS @@ -602,17 +590,17 @@ class ViewController: UIViewController { func presentAndRemoveMapview(_ navigationViewController: NavigationViewController, completion: CompletionHandler?) { navigationViewController.modalPresentationStyle = .fullScreen activeNavigationViewController = navigationViewController - + present(navigationViewController, animated: true) { [weak self] in completion?() - + self?.mapView = nil } } - + func beginCarPlayNavigation() { let delegate = UIApplication.shared.delegate as? AppDelegate - + if #available(iOS 12.0, *), let service = activeNavigationViewController?.navigationService, let location = service.router.location { @@ -620,13 +608,13 @@ class ViewController: UIViewController { navigationService: service) } } - + func endCarPlayNavigation(canceled: Bool) { if #available(iOS 12.0, *), let delegate = UIApplication.shared.delegate as? AppDelegate { delegate.carPlayManager.currentNavigator?.exitNavigation(byCanceling: canceled) } } - + func dismissActiveNavigationViewController() { activeNavigationViewController?.dismiss(animated: true) { self.activeNavigationViewController = nil @@ -642,13 +630,13 @@ class ViewController: UIViewController { let singleTap = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(tap:))) mapView.gestureRecognizers?.filter({ $0 is UILongPressGestureRecognizer }).forEach(singleTap.require(toFail:)) mapView.addGestureRecognizer(singleTap) - + trackLocations(mapView: mapView) mapView.showsUserLocation = true mapView.userTrackingMode = .follow mapView.showsUserHeadingIndicator = true } - + func uninstall(_ mapView: NavigationMapView) { NotificationCenter.default.removeObserver(self, name: .passiveLocationDataSourceDidUpdate, object: nil) mapView.removeFromSuperview() @@ -671,7 +659,7 @@ extension ViewController: MGLMapViewDelegate { self.mapView?.showWaypoints(on: currentRoute) } } - + func mapView(_ mapView: MGLMapView, strokeColorForShapeAnnotation annotation: MGLShape) -> UIColor { if annotation == trackPolyline { return .darkGray @@ -681,11 +669,11 @@ extension ViewController: MGLMapViewDelegate { } return .black } - + func mapView(_ mapView: MGLMapView, lineWidthForPolylineAnnotation annotation: MGLPolyline) -> CGFloat { return annotation == trackPolyline || annotation == rawTrackPolyline ? 4 : 1 } - + func mapViewRegionIsChanging(_ mapView: MGLMapView) { if activeNavigationViewController == nil, foundAllBuildings == false, let navMapView = mapView as? NavigationMapView { let buildingHighlightCoordinates = waypoints.compactMap { $0.targetCoordinate } @@ -724,12 +712,12 @@ extension ViewController: NavigationMapViewDelegate { let message = NSLocalizedString("REMOVE_WAYPOINT_CONFIRM_MSG", value: "Do you want to remove this waypoint?", comment: "Message of alert confirming waypoint removal") let removeTitle = NSLocalizedString("REMOVE_WAYPOINT_CONFIRM_REMOVE", value: "Remove Waypoint", comment: "Title of alert action for removing a waypoint") let cancelTitle = NSLocalizedString("REMOVE_WAYPOINT_CONFIRM_CANCEL", value: "Cancel", comment: "Title of action for dismissing waypoint removal confirmation sheet") - + let waypointRemovalAlertController = UIAlertController(title: title, message: message, preferredStyle: .alert) let removeAction = UIAlertAction(title: removeTitle, style: .destructive, handler: approve) let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel, handler: nil) [removeAction, cancelAction].forEach(waypointRemovalAlertController.addAction(_:)) - + self.present(waypointRemovalAlertController, animated: true, completion: nil) } } @@ -742,7 +730,7 @@ extension ViewController: RouteVoiceControllerDelegate { print(error) } - + // By default, the navigation service will attempt to filter out unqualified locations. // If however you would like to filter these locations in, // you can conditionally return a Bool here according to your own heuristics. @@ -750,7 +738,7 @@ extension ViewController: RouteVoiceControllerDelegate { func navigationViewController(_ navigationViewController: NavigationViewController, shouldDiscard location: CLLocation) -> Bool { return true } - + func navigationViewController(_ navigationViewController: NavigationViewController, shouldRerouteFrom location: CLLocation) -> Bool { return true } @@ -780,13 +768,13 @@ extension ViewController: NavigationViewControllerDelegate { func navigationViewController(_ navigationViewController: NavigationViewController, didArriveAt waypoint: Waypoint) -> Bool { // When the user arrives, present a view controller that prompts the user to continue to their next destination // This type of screen could show information about a destination, pickup/dropoff confirmation, instructions upon arrival, etc. - + //If we're not in a "Multiple Stops" demo, show the normal EORVC if navigationViewController.navigationService.router.routeProgress.isFinalLeg { endCarPlayNavigation(canceled: false) return true } - + guard let confirmationController = self.storyboard?.instantiateViewController(withIdentifier: "waypointConfirmation") as? WaypointConfirmationViewController else { return true } @@ -796,7 +784,7 @@ extension ViewController: NavigationViewControllerDelegate { navigationViewController.present(confirmationController, animated: true, completion: nil) return false } - + // Called when the user hits the exit button. // If implemented, you are responsible for also dismissing the UI. func navigationViewControllerDidDismiss(_ navigationViewController: NavigationViewController, byCanceling canceled: Bool) { @@ -816,7 +804,7 @@ extension ViewController: VisualInstructionDelegate { // let mutable = NSMutableAttributedString(attributedString: presented) // mutable.mutableString.applyTransform(.latinToKatakana, reverse: false, range: range, updatedRange: nil) // return mutable - + return presented } } @@ -827,36 +815,36 @@ extension ViewController { let dataSource = PassiveLocationDataSource() let locationManager = PassiveLocationManager(dataSource: dataSource) mapView.locationManager = locationManager - + NotificationCenter.default.addObserver(self, selector: #selector(didUpdatePassiveLocation), name: .passiveLocationDataSourceDidUpdate, object: dataSource) - + trackPolyline = nil rawTrackPolyline = nil } - + @objc func didUpdatePassiveLocation(_ notification: Notification) { if let roadName = notification.userInfo?[PassiveLocationDataSource.NotificationUserInfoKey.roadNameKey] as? String { title = roadName } - + if let location = notification.userInfo?[PassiveLocationDataSource.NotificationUserInfoKey.locationKey] as? CLLocation { if trackPolyline == nil { trackPolyline = MGLPolyline() } - + var coordinates: [CLLocationCoordinate2D] = [location.coordinate] trackPolyline?.appendCoordinates(&coordinates, count: UInt(coordinates.count)) } - + if let rawLocation = notification.userInfo?[PassiveLocationDataSource.NotificationUserInfoKey.rawLocationKey] as? CLLocation { if rawTrackPolyline == nil { rawTrackPolyline = MGLPolyline() } - + var coordinates: [CLLocationCoordinate2D] = [rawLocation.coordinate] rawTrackPolyline?.appendCoordinates(&coordinates, count: UInt(coordinates.count)) } - + mapView?.addAnnotations([rawTrackPolyline!, trackPolyline!]) } } From cfae0316edf8d5c9be5e9761ed7960fb838bcbc0 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Thu, 17 Dec 2020 12:01:24 -0800 Subject: [PATCH 15/48] Enable labeling of toll routes now that TollCollections are defined in MapboxDirections. --- Extensions/Route+Example.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Extensions/Route+Example.swift b/Extensions/Route+Example.swift index c176aa1e831..b54eef81572 100644 --- a/Extensions/Route+Example.swift +++ b/Extensions/Route+Example.swift @@ -4,15 +4,12 @@ import Turf extension Route { var tollIntersections: [Intersection]? { - return nil // Stubbed out until SDK includes new Intersection toll attributes - #if NavSDK_Includes_Toll_Intersections let allSteps = legs.compactMap { return $0.steps }.reduce([], +) let allIntersections = allSteps.compactMap { return $0.intersections }.reduce([], +) let intersectionsWithTolls = allIntersections.filter { return $0.tollCollection != nil } return intersectionsWithTolls - #endif } // returns the list of line segments along the route that fall within given bounding box. Returns nil if there are none. Line segments are defined by the route shape coordinates that lay within the bounding box From 72345f5c498e8def1fcfeb0a0dce62f945691ab4 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Fri, 15 Jan 2021 13:48:55 -0800 Subject: [PATCH 16/48] Update Base localization. --- Example/Base.lproj/Localizable.strings | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Example/Base.lproj/Localizable.strings b/Example/Base.lproj/Localizable.strings index 7a86c76898e..2955a6101a7 100644 --- a/Example/Base.lproj/Localizable.strings +++ b/Example/Base.lproj/Localizable.strings @@ -16,3 +16,9 @@ /* Title of alert confirming waypoint removal */ "REMOVE_WAYPOINT_CONFIRM_TITLE" = "Remove Waypoint?"; +/* This route does not have tolls */ +"ROUTE_HAS_NO_TOLLS" = "No Tolls"; + +/* This route does have tolls */ +"ROUTE_HAS_TOLLS" = "Tolls"; + From 0ece86b0c01ef97acb0feb028b22ef3993bae23c Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Fri, 15 Jan 2021 14:12:53 -0800 Subject: [PATCH 17/48] Incorporate review feedback. --- Example/ViewController.swift | 19 +++++++------------ Extensions/Route+Example.swift | 2 +- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 1a7a63f9926..2d339cf36d9 100755 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -104,20 +104,13 @@ class ViewController: UIViewController { // First, we will look for a set of RouteSteps unique to each route var excludedSteps = [RouteStep]() for (index, route) in routes.enumerated() { - let allSteps = route.legs.compactMap { return $0.steps }.reduce([], +) - let alternateSteps = allSteps.filter { step -> Bool in - for existingStep in excludedSteps { - if step == existingStep { - return false - } - } - return true - } + let allSteps = route.legs.flatMap { return $0.steps } + let alternateSteps = allSteps.filter { !excludedSteps.contains($0) } excludedSteps.append(contentsOf: alternateSteps) let visibleAlternateSteps = alternateSteps.filter { $0.intersects(visibleBoundingBox) } - var coordinate = kCLLocationCoordinate2DInvalid + var coordinate: CLLocationCoordinate2D? // Obtain a polyline of the set of steps. We'll look for a good spot along this line to place the annotation. // We will consider a good spot to be somewhere near the middle of the line, making sure that the coordinate is visible on-screen. @@ -155,17 +148,19 @@ class ViewController: UIViewController { } } + guard let annotationCoordinate = coordinate else { return } + // form the appropriate text string for the annotation let labelText = self.annotationLabelForRoute(route, tolls: routesContainTolls) // Create the feature for this route annotation. Set the styling attributes that will be used to render the annotation in the style layer. let point = MGLPointFeature() - point.coordinate = coordinate + point.coordinate = annotationCoordinate var tailPosition = randomTailPosition // convert our coordinate to screen space so we can make a choice on which side of the coordinate the label ends up on - let unprojectedCoordinate = mapView.convert(coordinate, toPointTo: nil) + let unprojectedCoordinate = mapView.convert(annotationCoordinate, toPointTo: nil) // pick the orientation of the bubble "stem" based on how close to the edge of the screen it is if tailPosition == .left && unprojectedCoordinate.x > mapView.bounds.width * 0.75 { diff --git a/Extensions/Route+Example.swift b/Extensions/Route+Example.swift index b54eef81572..553ff9a1f6f 100644 --- a/Extensions/Route+Example.swift +++ b/Extensions/Route+Example.swift @@ -4,7 +4,7 @@ import Turf extension Route { var tollIntersections: [Intersection]? { - let allSteps = legs.compactMap { return $0.steps }.reduce([], +) + let allSteps = legs.flatMap { return $0.steps } let allIntersections = allSteps.compactMap { return $0.intersections }.reduce([], +) let intersectionsWithTolls = allIntersections.filter { return $0.tollCollection != nil } From 999586c163bab11342a965bceb481b7e8ff47488 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Fri, 15 Jan 2021 20:27:15 -0800 Subject: [PATCH 18/48] Move Route Duration Annotations to NavigationMapView instead of Example project. Entails also moving a number of class extensions and image resources over as well. --- Bench/Assets.xcassets/Contents.json | 6 +- Example/Base.lproj/Localizable.strings | 6 - Example/ViewController.swift | 222 +---------------- Extensions/Locale+Example.swift | 13 - Extensions/Route+Example.swift | 83 ------- Extensions/UIImage+Example.swift | 28 --- MapboxNavigation.xcodeproj/project.pbxproj | 36 +-- .../RouteAnnotations/Contents.json | 0 .../Contents.json | 0 .../LeftHandUpDown.pdf | Bin .../Contents.json | 0 .../RightHandUpDown.pdf | Bin .../Turf.swift | 0 Sources/MapboxNavigation/Array.swift | 22 ++ Sources/MapboxNavigation/DayStyle.swift | 3 + .../MapboxNavigation/NavigationMapView.swift | 229 +++++++++++++++++- .../Resources/Base.lproj/Localizable.strings | 6 + Sources/MapboxNavigation/Route.swift | 58 +++++ Sources/MapboxNavigation/UIImage.swift | 25 ++ 19 files changed, 354 insertions(+), 383 deletions(-) delete mode 100644 Extensions/Locale+Example.swift delete mode 100644 Extensions/Route+Example.swift delete mode 100644 Extensions/UIImage+Example.swift rename {Example => MapboxNavigation/Resources}/Assets.xcassets/RouteAnnotations/Contents.json (100%) rename {Example => MapboxNavigation/Resources}/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/Contents.json (100%) rename {Example => MapboxNavigation/Resources}/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/LeftHandUpDown.pdf (100%) rename {Example => MapboxNavigation/Resources}/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/Contents.json (100%) rename {Example => MapboxNavigation/Resources}/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/RightHandUpDown.pdf (100%) rename Extensions/Turf+Example.swift => MapboxNavigation/Turf.swift (100%) diff --git a/Bench/Assets.xcassets/Contents.json b/Bench/Assets.xcassets/Contents.json index da4a164c918..73c00596a7f 100644 --- a/Bench/Assets.xcassets/Contents.json +++ b/Bench/Assets.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Example/Base.lproj/Localizable.strings b/Example/Base.lproj/Localizable.strings index 2955a6101a7..7a86c76898e 100644 --- a/Example/Base.lproj/Localizable.strings +++ b/Example/Base.lproj/Localizable.strings @@ -16,9 +16,3 @@ /* Title of alert confirming waypoint removal */ "REMOVE_WAYPOINT_CONFIRM_TITLE" = "Remove Waypoint?"; -/* This route does not have tolls */ -"ROUTE_HAS_NO_TOLLS" = "No Tolls"; - -/* This route does have tolls */ -"ROUTE_HAS_TOLLS" = "Tolls"; - diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 2d339cf36d9..de20cb4c8a9 100755 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -54,218 +54,8 @@ class ViewController: UIViewController { startButton.isEnabled = true mapView?.show(routes) mapView?.showWaypoints(on: currentRoute) - if let style = mapView?.style { - // show route Duration and Toll annotations - updateRouteAnnotations(routes, style: style) - } - } - } - - private func updateAnnotationSymbolImages(_ style: MGLStyle) { - let capInsetHeight = CGFloat(22) - let capInsetWidth = CGFloat(11) - let capInsets = UIEdgeInsets(top: capInsetHeight, left: capInsetWidth, bottom: capInsetHeight, right: capInsetWidth) - if let image = UIImage(named: "RouteInfoAnnotationLeftHanded") { - let regularRouteImage = image.tint(UIColor.white).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) - style.setImage(regularRouteImage, forName: "RouteInfoAnnotationLeftHanded") - - let selectedRouteImage = image.tint(#colorLiteral(red: 0.337254902, green: 0.6588235294, blue: 0.9843137255, alpha: 1)).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) - style.setImage(selectedRouteImage, forName: "RouteInfoAnnotationLeftHanded-Selected") - } - - if let image = UIImage(named: "RouteInfoAnnotationRightHanded") { - let regularRouteImage = image.tint(UIColor.white).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) - style.setImage(regularRouteImage, forName: "RouteInfoAnnotationRightHanded") - - let selectedRouteImage = image.tint(#colorLiteral(red: 0.337254902, green: 0.6588235294, blue: 0.9843137255, alpha: 1)).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) - style.setImage(selectedRouteImage, forName: "RouteInfoAnnotationRightHanded-Selected") - } - } - - private func updateRouteAnnotations(_ routes: [Route]?, style: MGLStyle) { - // remove any existing route annotation - removeRouteAnnotationsLayerFromStyle(style) - - guard self.waypoints.count > 0, let routes = routes, let mapView = self.mapView else { return } - - let visibleBoundingBox = BoundingBox(coordinateBounds: mapView.visibleCoordinateBounds) - - let tollRoutes = routes.filter { route -> Bool in - return (route.tollIntersections?.count ?? 0) > 0 - } - let routesContainTolls = tollRoutes.count > 0 - - // pick a random tail direction to keep things varied - guard let randomTailPosition = [RouteDurationAnnotationTailPosition.left, RouteDurationAnnotationTailPosition.right].randomElement() else { return } - - var features = [MGLPointFeature]() - - // Run through our heuristic algorithm looking for a good coordinate along each route line to place it's route annotation - // First, we will look for a set of RouteSteps unique to each route - var excludedSteps = [RouteStep]() - for (index, route) in routes.enumerated() { - let allSteps = route.legs.flatMap { return $0.steps } - let alternateSteps = allSteps.filter { !excludedSteps.contains($0) } - - excludedSteps.append(contentsOf: alternateSteps) - let visibleAlternateSteps = alternateSteps.filter { $0.intersects(visibleBoundingBox) } - - var coordinate: CLLocationCoordinate2D? - - // Obtain a polyline of the set of steps. We'll look for a good spot along this line to place the annotation. - // We will consider a good spot to be somewhere near the middle of the line, making sure that the coordinate is visible on-screen. - if let continuousLine = visibleAlternateSteps.continuousShape(), continuousLine.coordinates.count > 0 { - coordinate = continuousLine.coordinates[0] - - // Pick a coordinate using some randomness in order to give visual variety. - // Take care to snap that coordinate to one that lays on the original route line. - // If the chosen snapped coordinate is not visible onthe screen, then we walk back along the route coordinates looking for one that is. - // If none of the earlier points are on screen then we walk forward along the route coordinates until we find one that is. - if let distance = continuousLine.distance(), let sampleCoordinate = continuousLine.indexedCoordinateFromStart(distance: distance * CLLocationDistance.random(in: 0.3...0.8))?.coordinate, let routeShape = route.shape, let snappedCoordinate = routeShape.closestCoordinate(to: sampleCoordinate) { - var foundOnscreenCoordinate = false - var firstOnscreenCoordinate = snappedCoordinate.coordinate - for indexedCoordinate in routeShape.coordinates.prefix(through: snappedCoordinate.index).reversed() { - if visibleBoundingBox.contains(indexedCoordinate) { - firstOnscreenCoordinate = indexedCoordinate - foundOnscreenCoordinate = true - break - } - } - - if foundOnscreenCoordinate { - // We found a point that is both on the route and on-screen - coordinate = firstOnscreenCoordinate - } else { - // we didn't find a previous point that is on-screen so we'll move forward through the coordinates looking for one - for indexedCoordinate in routeShape.coordinates.suffix(from: snappedCoordinate.index) { - if visibleBoundingBox.contains(indexedCoordinate) { - firstOnscreenCoordinate = indexedCoordinate - break - } - } - coordinate = firstOnscreenCoordinate - } - } - } - - guard let annotationCoordinate = coordinate else { return } - - // form the appropriate text string for the annotation - let labelText = self.annotationLabelForRoute(route, tolls: routesContainTolls) - - // Create the feature for this route annotation. Set the styling attributes that will be used to render the annotation in the style layer. - let point = MGLPointFeature() - point.coordinate = annotationCoordinate - - var tailPosition = randomTailPosition - - // convert our coordinate to screen space so we can make a choice on which side of the coordinate the label ends up on - let unprojectedCoordinate = mapView.convert(annotationCoordinate, toPointTo: nil) - - // pick the orientation of the bubble "stem" based on how close to the edge of the screen it is - if tailPosition == .left && unprojectedCoordinate.x > mapView.bounds.width * 0.75 { - tailPosition = .right - } else if tailPosition == .right && unprojectedCoordinate.x < mapView.bounds.width * 0.25 { - tailPosition = .left - } - - var imageName = tailPosition == .left ? "RouteInfoAnnotationLeftHanded" : "RouteInfoAnnotationRightHanded" - - // the selected route uses the colored annotation image - if index == 0 { - imageName += "-Selected" - } - - // set the feature attributes which will be used in styling the symbol style layer - point.attributes = ["selected": index == 0, "tailPosition": tailPosition.rawValue, "text": labelText, "imageName": imageName, "sortOrder": -index] - - features.append(point) - } - - // add the features to the style - self.addRouteAnnotationSymbolLayer(features: features, style: style) - } - - private let annotationLayerIdentifier = "RouteDurationAnnotations" - - private func addRouteAnnotationSymbolLayer(features: [MGLPointFeature], style: MGLStyle) { - let dataSource: MGLShapeSource - if let source = style.source(withIdentifier: annotationLayerIdentifier + "-source") as? MGLShapeSource { - dataSource = source - } else { - dataSource = MGLShapeSource(identifier: annotationLayerIdentifier + "-source", features: features, options: nil) - style.addSource(dataSource) - } - - let shapeLayer: MGLSymbolStyleLayer - - if let layer = style.layer(withIdentifier: annotationLayerIdentifier + "-shape") as? MGLSymbolStyleLayer { - shapeLayer = layer - } else { - shapeLayer = MGLSymbolStyleLayer(identifier: annotationLayerIdentifier + "-shape", source: dataSource) - } - - shapeLayer.text = NSExpression(forKeyPath: "text") - let fontSizeByZoomLevel = [ - 13: NSExpression(forConstantValue: 16), - 15.5: NSExpression(forConstantValue: 20) - ] - shapeLayer.textFontSize = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", fontSizeByZoomLevel) - - shapeLayer.textColor = NSExpression(forConditional: NSPredicate(format: "selected == true"), - trueExpression: NSExpression(forConstantValue: UIColor.white), - falseExpression: NSExpression(forConstantValue: UIColor.black)) - - shapeLayer.textFontNames = NSExpression(forConstantValue: ["DIN Pro Medium"]) - shapeLayer.textAllowsOverlap = NSExpression(forConstantValue: true) - shapeLayer.textJustification = NSExpression(forConstantValue: "left") - shapeLayer.symbolZOrder = NSExpression(forConstantValue: NSValue(mglSymbolZOrder: MGLSymbolZOrder.auto)) - shapeLayer.symbolSortKey = NSExpression(forConditional: NSPredicate(format: "selected == true"), - trueExpression: NSExpression(forConstantValue: 1), - falseExpression: NSExpression(format: "sortOrder")) - shapeLayer.iconAnchor = NSExpression(forConditional: NSPredicate(format: "tailPosition == 0"), - trueExpression: NSExpression(forConstantValue: "bottom-left"), - falseExpression: NSExpression(forConstantValue: "bottom-right")) - shapeLayer.textAnchor = shapeLayer.iconAnchor - shapeLayer.iconTextFit = NSExpression(forConstantValue: "both") - - shapeLayer.iconImageName = NSExpression(forKeyPath: "imageName") - shapeLayer.iconOffset = NSExpression(forConditional: NSPredicate(format: "tailPosition == 0"), - trueExpression: NSExpression(forConstantValue: CGVector(dx: 0.5, dy: -1.0)), - falseExpression: NSExpression(forConstantValue: CGVector(dx: -0.5, dy: -1.0))) - shapeLayer.textOffset = shapeLayer.iconOffset - shapeLayer.iconAllowsOverlap = NSExpression(forConstantValue: true) - - style.addLayer(shapeLayer) - } - - private func removeRouteAnnotationsLayerFromStyle(_ style: MGLStyle) { - if let annotationsLayer = style.layer(withIdentifier: annotationLayerIdentifier + "-shape") { - style.removeLayer(annotationsLayer) - } - - if let annotationsSource = style.source(withIdentifier: annotationLayerIdentifier + "-source") { - style.removeSource(annotationsSource) - } - } - - // This function generates the text for the label to be shown on screen. It will include estimated duration and info on Tolls, if applicable - private func annotationLabelForRoute(_ route: Route, tolls: Bool) -> String { - var eta = DateComponentsFormatter.shortDateComponentsFormatter.string(from: route.expectedTravelTime) ?? "" - - let hasTolls = (route.tollIntersections?.count ?? 0) > 0 - if hasTolls { - eta += "\n" + NSLocalizedString("ROUTE_HAS_TOLLS", value: "Tolls", comment: "This route does have tolls") - if let symbol = Locale.current.currencySymbol { - eta += " " + symbol - } - } else if tolls { - // If one of the routes has tolls, but this one does not then it needs to explicitly say that it has no tolls - // If no routes have tolls at all then we can omit this portion of the string. - eta += "\n" + NSLocalizedString("ROUTE_HAS_NO_TOLLS", value: "No Tolls", comment: "This route does not have tolls") + mapView?.showRouteDurationAnnotations(routes) } - - return eta } weak var activeNavigationViewController: NavigationViewController? @@ -404,11 +194,11 @@ class ViewController: UIViewController { longPressHintView.isHidden = false if let style = mapView?.style { - removeRouteAnnotationsLayerFromStyle(style) style.removeDebugLineLayers() style.removeDebugCircleLayers() } + mapView?.removeRouteDurationAnnotations() mapView?.unhighlightBuildings() mapView?.removeRoutes() mapView?.removeWaypoints() @@ -646,12 +436,11 @@ extension ViewController: MGLMapViewDelegate { self.mapView?.localizeLabels() - self.updateAnnotationSymbolImages(style) - if let routes = response?.routes, let currentRoute = routes.first, let coords = currentRoute.shape?.coordinates { mapView.setVisibleCoordinateBounds(MGLPolygon(coordinates: coords, count: UInt(coords.count)).overlayBounds, animated: false) self.mapView?.show(routes) self.mapView?.showWaypoints(on: currentRoute) + self.mapView?.showRouteDurationAnnotations(routes) } } @@ -679,9 +468,8 @@ extension ViewController: MGLMapViewDelegate { } func mapView(_ mapView: MGLMapView, regionDidChangeWith reason: MGLCameraChangeReason, animated: Bool) { - if let style = mapView.style, let routes = response?.routes { - updateRouteAnnotations(routes, style: style) - } + guard reason != .programmatic, activeNavigationViewController == nil, let routes = response?.routes else { return } + self.mapView?.showRouteDurationAnnotations(routes) } } diff --git a/Extensions/Locale+Example.swift b/Extensions/Locale+Example.swift deleted file mode 100644 index 427efd4e5fa..00000000000 --- a/Extensions/Locale+Example.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -extension Locale { - var currencySymbol: String? { - guard let currencyCode = currencyCode else { return nil } - let locale = NSLocale(localeIdentifier: currencyCode) - if locale.displayName(forKey: .currencySymbol, value: currencyCode) == currencyCode { - let newlocale = NSLocale(localeIdentifier: currencyCode.dropLast() + "_en") - return newlocale.displayName(forKey: .currencySymbol, value: currencyCode) - } - return locale.displayName(forKey: .currencySymbol, value: currencyCode) - } -} diff --git a/Extensions/Route+Example.swift b/Extensions/Route+Example.swift deleted file mode 100644 index 553ff9a1f6f..00000000000 --- a/Extensions/Route+Example.swift +++ /dev/null @@ -1,83 +0,0 @@ -import Foundation -import MapboxDirections -import Turf - -extension Route { - var tollIntersections: [Intersection]? { - let allSteps = legs.flatMap { return $0.steps } - - let allIntersections = allSteps.compactMap { return $0.intersections }.reduce([], +) - let intersectionsWithTolls = allIntersections.filter { return $0.tollCollection != nil } - - return intersectionsWithTolls - } - - // returns the list of line segments along the route that fall within given bounding box. Returns nil if there are none. Line segments are defined by the route shape coordinates that lay within the bounding box - func shapes(within: Turf.BoundingBox) -> [LineString]? { - guard let coordinates = shape?.coordinates else { return nil } - var lines = [[CLLocationCoordinate2D]]() - var currentLine: [CLLocationCoordinate2D]? - for coordinate in coordinates { - // see if this coordinate lays within the bounds - if within.contains(coordinate) { - // if there is no current line segment then start one - if currentLine == nil { - currentLine = [CLLocationCoordinate2D]() - } - - // append the coordinate to the current line segment - currentLine?.append(coordinate) - } else { - // if there is a current line segment being built then finish it off and reset - if let currentLine = currentLine { - lines.append(currentLine) - } - currentLine = nil - } - } - - // append any outstanding final segment - if let currentLine = currentLine { - lines.append(currentLine) - } - currentLine = nil - - // return the segments as LineStrings - return lines.compactMap { coordinateList -> LineString? in - return LineString(coordinateList) - } - } -} - -extension RouteStep { - func intersects(_ boundingBox: Turf.BoundingBox) -> Bool { - guard let coordinates = shape?.coordinates else { return false } - - for coordinate in coordinates { - if boundingBox.contains(coordinate) { - return true - } - } - return false - } -} - -extension Array where Element == RouteStep { - // Find the longest contiguous series of RouteSteps connected to the first one. - // - // tolerance: -- Maximum distance between the end of one RouteStep and the start of the next to still consider them connected. Defaults to 100 meters - func continuousShape(tolerance: CLLocationDistance = 100) -> LineString? { - guard count > 0 else { return nil } - guard count > 1 else { return self[0].shape } - var continuousLine = [CLLocationCoordinate2D]() - - for index in 0...count-2 { - if let currentStepFinalCoordinate = self[index].shape?.coordinates.last, currentStepFinalCoordinate.distance(to: self[index+1].maneuverLocation) < tolerance, let coordinates = self[index].shape?.coordinates { - continuousLine.append(contentsOf: coordinates) - } - } - - return LineString(continuousLine) - } -} - diff --git a/Extensions/UIImage+Example.swift b/Extensions/UIImage+Example.swift deleted file mode 100644 index 0480f249308..00000000000 --- a/Extensions/UIImage+Example.swift +++ /dev/null @@ -1,28 +0,0 @@ -import UIKit - -extension UIImage { - func tint(_ tintColor: UIColor) -> UIImage { - let imageSize = size - let imageScale = scale - let contextBounds = CGRect(origin: .zero, size: imageSize) - - UIGraphicsBeginImageContextWithOptions(imageSize, false, imageScale) - - defer { UIGraphicsEndImageContext() } - - UIColor.black.setFill() - UIRectFill(contextBounds) - draw(at: .zero) - - guard let imageOverBlack = UIGraphicsGetImageFromCurrentImageContext() else { return self } - tintColor.setFill() - UIRectFill(contextBounds) - - imageOverBlack.draw(at: .zero, blendMode: .multiply, alpha: 1) - draw(at: .zero, blendMode: .destinationIn, alpha: 1) - - guard let finalImage = UIGraphicsGetImageFromCurrentImageContext() else { return self } - - return finalImage - } -} diff --git a/MapboxNavigation.xcodeproj/project.pbxproj b/MapboxNavigation.xcodeproj/project.pbxproj index 7b69f08ed9c..addd644d76a 100644 --- a/MapboxNavigation.xcodeproj/project.pbxproj +++ b/MapboxNavigation.xcodeproj/project.pbxproj @@ -402,17 +402,10 @@ DADD82802161EC0300B8B47D /* UIViewAnimationOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADD827F2161EC0300B8B47D /* UIViewAnimationOptionsTests.swift */; }; DAE22A2921C9DEDA00CA269D /* MGLVectorTileSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE22A2821C9DEDA00CA269D /* MGLVectorTileSourceTests.swift */; }; DAFA92071F01735000A7FB09 /* DistanceFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 351BEC0B1E5BCC72006FE110 /* DistanceFormatter.swift */; }; - F405206A255A171800F3DD47 /* Turf+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052069255A171800F3DD47 /* Turf+Example.swift */; }; - F4052084255A178C00F3DD47 /* Route+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052083255A178C00F3DD47 /* Route+Example.swift */; }; - F405208C255A17D300F3DD47 /* Locale+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F405208B255A17D300F3DD47 /* Locale+Example.swift */; }; - F405209A255A180500F3DD47 /* UIImage+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052099255A180500F3DD47 /* UIImage+Example.swift */; }; + F452634C25B2506A00F7A464 /* Turf.swift in Sources */ = {isa = PBXBuildFile; fileRef = F452634B25B2506A00F7A464 /* Turf.swift */; }; F4BF512E24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BF512D24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift */; }; F4C5A26F24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C5A26E24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift */; }; F4FD7739256D886E007C525A /* DateComponentsFormatter+MGLNavigationAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FD7738256D886E007C525A /* DateComponentsFormatter+MGLNavigationAdditions.swift */; }; - F4FD7740256D89C6007C525A /* Turf+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052069255A171800F3DD47 /* Turf+Example.swift */; }; - F4FD7747256D89CA007C525A /* Route+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052083255A178C00F3DD47 /* Route+Example.swift */; }; - F4FD774E256D89CD007C525A /* Locale+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F405208B255A17D300F3DD47 /* Locale+Example.swift */; }; - F4FD7755256D89D0007C525A /* UIImage+Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4052099255A180500F3DD47 /* UIImage+Example.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1055,10 +1048,7 @@ DAFEB36D2093A11F00A86A83 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; DAFEB36E2093A3E000A86A83 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; DAFEB36F2093A3EF00A86A83 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ko; path = Resources/ko.lproj/Localizable.stringsdict; sourceTree = ""; }; - F4052069255A171800F3DD47 /* Turf+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Turf+Example.swift"; sourceTree = ""; }; - F4052083255A178C00F3DD47 /* Route+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Route+Example.swift"; sourceTree = ""; }; - F405208B255A17D300F3DD47 /* Locale+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+Example.swift"; sourceTree = ""; }; - F4052099255A180500F3DD47 /* UIImage+Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Example.swift"; sourceTree = ""; }; + F452634B25B2506A00F7A464 /* Turf.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Turf.swift; sourceTree = ""; }; F4BF512D24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackSubtypeCollectionViewCell.swift; sourceTree = ""; }; F4C5A26E24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackSubtypeViewController.swift; sourceTree = ""; }; F4FD7738256D886E007C525A /* DateComponentsFormatter+MGLNavigationAdditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateComponentsFormatter+MGLNavigationAdditions.swift"; sourceTree = ""; }; @@ -1436,7 +1426,6 @@ 3529FCF121A5C5B300AEA9AA /* ResizableView.swift */, 3529FCF521A5C5D900AEA9AA /* UIViewController.swift */, 3529FCF721A5C62400AEA9AA /* SettingsViewController.swift */, - F40520A1255A183B00F3DD47 /* Extensions */, ); name = Example; sourceTree = ""; @@ -1705,6 +1694,7 @@ 350E2C5E22707EB80014CEB3 /* UIScreen.swift */, CFD47D8F20FD85EC00BC1E49 /* MGLAccountManager.swift */, F4FD7738256D886E007C525A /* DateComponentsFormatter+MGLNavigationAdditions.swift */, + F452634B25B2506A00F7A464 /* Turf.swift */, ); name = Extensions; sourceTree = ""; @@ -1871,17 +1861,6 @@ path = Sources/CMapboxCoreNavigation; sourceTree = ""; }; - F40520A1255A183B00F3DD47 /* Extensions */ = { - isa = PBXGroup; - children = ( - F4052069255A171800F3DD47 /* Turf+Example.swift */, - F4052083255A178C00F3DD47 /* Route+Example.swift */, - F405208B255A17D300F3DD47 /* Locale+Example.swift */, - F4052099255A180500F3DD47 /* UIImage+Example.swift */, - ); - path = Extensions; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -2561,6 +2540,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F452634C25B2506A00F7A464 /* Turf.swift in Sources */, 351BEBF11E5BCC63006FE110 /* MGLMapView.swift in Sources */, DA443DDE2278C90E00ED1307 /* CPTrip.swift in Sources */, 2B72EC5E241276D10003B370 /* RouteVoiceController.swift in Sources */, @@ -2680,15 +2660,11 @@ 358D14681E5E3B7700ADE590 /* ViewController.swift in Sources */, 3529FCF821A5C62400AEA9AA /* SettingsViewController.swift in Sources */, 3529FCF421A5C5C600AEA9AA /* SettingsItems.swift in Sources */, - F4052084255A178C00F3DD47 /* Route+Example.swift in Sources */, 3529FCF221A5C5B400AEA9AA /* ResizableView.swift in Sources */, C5D9800D1EFA8BA9006DBF2E /* CustomViewController.swift in Sources */, - F405206A255A171800F3DD47 /* Turf+Example.swift in Sources */, - F405209A255A180500F3DD47 /* UIImage+Example.swift in Sources */, AED6285622CBE4CE00058A51 /* ViewController+GuidanceCards.swift in Sources */, C51FC31720F689F800400CE7 /* CustomStyles.swift in Sources */, 6441B16A1EFC64E50076499F /* WaypointConfirmationViewController.swift in Sources */, - F405208C255A17D300F3DD47 /* Locale+Example.swift in Sources */, 358D14661E5E3B7700ADE590 /* AppDelegate.swift in Sources */, 3529FCF621A5C5D900AEA9AA /* UIViewController.swift in Sources */, ); @@ -2789,15 +2765,11 @@ C53F2EE420EBC95600D9798F /* ViewController.swift in Sources */, C53F2EE520EBC95600D9798F /* CustomViewController.swift in Sources */, DA303CAA21B7A93400F921DC /* ResizableView.swift in Sources */, - F4FD7740256D89C6007C525A /* Turf+Example.swift in Sources */, - F4FD7755256D89D0007C525A /* UIImage+Example.swift in Sources */, C53F2EE720EBC95600D9798F /* WaypointConfirmationViewController.swift in Sources */, C5DE4B6220F6B6B3007AFBE6 /* CustomStyles.swift in Sources */, - F4FD774E256D89CD007C525A /* Locale+Example.swift in Sources */, DA8805002316EAED00B54D87 /* ViewController+GuidanceCards.swift in Sources */, 35379CFD21480C0500FD402E /* AppDelegate+CarPlay.swift in Sources */, C53F2EE820EBC95600D9798F /* AppDelegate.swift in Sources */, - F4FD7747256D89CA007C525A /* Route+Example.swift in Sources */, DA303CA621B7A90100F921DC /* UIViewController.swift in Sources */, 3577B878214FF35800094294 /* FavoritesList.swift in Sources */, DA303CA921B7A93100F921DC /* SettingsItems.swift in Sources */, diff --git a/Example/Assets.xcassets/RouteAnnotations/Contents.json b/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/Contents.json similarity index 100% rename from Example/Assets.xcassets/RouteAnnotations/Contents.json rename to MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/Contents.json diff --git a/Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/Contents.json b/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/Contents.json similarity index 100% rename from Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/Contents.json rename to MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/Contents.json diff --git a/Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/LeftHandUpDown.pdf b/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/LeftHandUpDown.pdf similarity index 100% rename from Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/LeftHandUpDown.pdf rename to MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/LeftHandUpDown.pdf diff --git a/Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/Contents.json b/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/Contents.json similarity index 100% rename from Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/Contents.json rename to MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/Contents.json diff --git a/Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/RightHandUpDown.pdf b/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/RightHandUpDown.pdf similarity index 100% rename from Example/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/RightHandUpDown.pdf rename to MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/RightHandUpDown.pdf diff --git a/Extensions/Turf+Example.swift b/MapboxNavigation/Turf.swift similarity index 100% rename from Extensions/Turf+Example.swift rename to MapboxNavigation/Turf.swift diff --git a/Sources/MapboxNavigation/Array.swift b/Sources/MapboxNavigation/Array.swift index f35cf1713ff..ebaa7cd74d0 100644 --- a/Sources/MapboxNavigation/Array.swift +++ b/Sources/MapboxNavigation/Array.swift @@ -1,3 +1,6 @@ +import MapboxDirections +import Turf + extension Array { /** Conditionally remove each element depending on the elements immediately preceding and following it. @@ -32,3 +35,22 @@ extension Array where Element: NSAttributedString { return joinedAttributedString } } + +extension Array where Element == RouteStep { + // Find the longest contiguous series of RouteSteps connected to the first one. + // + // tolerance: -- Maximum distance between the end of one RouteStep and the start of the next to still consider them connected. Defaults to 100 meters + func continuousShape(tolerance: CLLocationDistance = 100) -> LineString? { + guard count > 0 else { return nil } + guard count > 1 else { return self[0].shape } + var continuousLine = [CLLocationCoordinate2D]() + + for index in 0...count-2 { + if let currentStepFinalCoordinate = self[index].shape?.coordinates.last, currentStepFinalCoordinate.distance(to: self[index+1].maneuverLocation) < tolerance, let coordinates = self[index].shape?.coordinates { + continuousLine.append(contentsOf: coordinates) + } + } + + return LineString(continuousLine) + } +} diff --git a/Sources/MapboxNavigation/DayStyle.swift b/Sources/MapboxNavigation/DayStyle.swift index 91dfc5de31c..c8e1a19c680 100644 --- a/Sources/MapboxNavigation/DayStyle.swift +++ b/Sources/MapboxNavigation/DayStyle.swift @@ -30,6 +30,8 @@ extension UIColor { class var defaultBuildingColor: UIColor { get { return #colorLiteral(red: 0.9833194452, green: 0.9843137255, blue: 0.9331936657, alpha: 0.8019049658) } } class var defaultBuildingHighlightColor: UIColor { get { return #colorLiteral(red: 0.337254902, green: 0.6588235294, blue: 0.9843137255, alpha: 0.949406036) } } + + class var defaultRouteDurationAnnotationSelectedColor: UIColor { get { return #colorLiteral(red: 0.337254902, green: 0.6588235294, blue: 0.9843137255, alpha: 1) } } } extension UIColor { @@ -153,6 +155,7 @@ open class DayStyle: Style { NavigationMapView.appearance().trafficUnknownColor = .trafficUnknown NavigationMapView.appearance().buildingDefaultColor = .defaultBuildingColor NavigationMapView.appearance().buildingHighlightColor = .defaultBuildingHighlightColor + NavigationMapView.appearance().routeDurationAnnotationSelectedColor = .defaultRouteDurationAnnotationSelectedColor NavigationView.appearance().backgroundColor = #colorLiteral(red: 0.764706, green: 0.752941, blue: 0.733333, alpha: 1) NextBannerView.appearance().backgroundColor = #colorLiteral(red: 0.9675388083, green: 0.9675388083, blue: 0.9675388083, alpha: 1) NextBannerView.appearance(whenContainedInInstancesOf:[InstructionsCardContainerView.self]).backgroundColor = #colorLiteral(red: 0.9675388083, green: 0.9675388083, blue: 0.9675388083, alpha: 1) diff --git a/Sources/MapboxNavigation/NavigationMapView.swift b/Sources/MapboxNavigation/NavigationMapView.swift index c4f937d6aa4..7353a4ece49 100755 --- a/Sources/MapboxNavigation/NavigationMapView.swift +++ b/Sources/MapboxNavigation/NavigationMapView.swift @@ -128,7 +128,9 @@ open class NavigationMapView: MGLMapView, UIGestureRecognizerDelegate { userCourseView = reducedAccuracyActivatedMode ? UserHaloCourseView(frame: CGRect(origin: .zero, size: 75.0)) : UserPuckCourseView(frame: CGRect(origin: .zero, size: 75.0)) } } - + + @objc dynamic public var routeDurationAnnotationSelectedColor: UIColor = .defaultRouteDurationAnnotationSelectedColor + var userLocationForCourseTracking: CLLocation? var animatesUserLocation: Bool = false var altitude: CLLocationDistance @@ -958,6 +960,226 @@ open class NavigationMapView: MGLMapView, UIGestureRecognizerDelegate { SourceIdentifier.arrowSymbol, ].compactMap { style.source(withIdentifier: $0) })) } + + public func showRouteDurationAnnotations(_ routes: [Route]?) { + guard let style = style else { return } + updateAnnotationSymbolImages(style) + updateRouteDurationAnnotations(routes, style: style) + } + + private func updateAnnotationSymbolImages(_ style: MGLStyle) { + guard style.image(forName: "RouteInfoAnnotationLeftHanded") == nil, style.image(forName: "RouteInfoAnnotationRightHanded") == nil else { return } + let capInsetHeight = CGFloat(22) + let capInsetWidth = CGFloat(11) + let capInsets = UIEdgeInsets(top: capInsetHeight, left: capInsetWidth, bottom: capInsetHeight, right: capInsetWidth) + if let image = Bundle.mapboxNavigation.image(named: "RouteInfoAnnotationLeftHanded") { + let regularRouteImage = image.tint(UIColor.white).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) + style.setImage(regularRouteImage, forName: "RouteInfoAnnotationLeftHanded") + + let selectedRouteImage = image.tint(routeDurationAnnotationSelectedColor).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) + style.setImage(selectedRouteImage, forName: "RouteInfoAnnotationLeftHanded-Selected") + } + + if let image = Bundle.mapboxNavigation.image(named: "RouteInfoAnnotationRightHanded") { + let regularRouteImage = image.tint(UIColor.white).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) + style.setImage(regularRouteImage, forName: "RouteInfoAnnotationRightHanded") + + let selectedRouteImage = image.tint(routeDurationAnnotationSelectedColor).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) + style.setImage(selectedRouteImage, forName: "RouteInfoAnnotationRightHanded-Selected") + } + } + + private func updateRouteDurationAnnotations(_ routes: [Route]?, style: MGLStyle) { + // remove any existing route annotation + removeRouteDurationAnnotationsLayerFromStyle(style) + + guard let routes = routes else { return } + + let visibleBoundingBox = BoundingBox(coordinateBounds: visibleCoordinateBounds) + + let tollRoutes = routes.filter { route -> Bool in + return (route.tollIntersections?.count ?? 0) > 0 + } + let routesContainTolls = tollRoutes.count > 0 + + // pick a random tail direction to keep things varied + guard let randomTailPosition = [RouteDurationAnnotationTailPosition.left, RouteDurationAnnotationTailPosition.right].randomElement() else { return } + + var features = [MGLPointFeature]() + + // Run through our heuristic algorithm looking for a good coordinate along each route line to place it's route annotation + // First, we will look for a set of RouteSteps unique to each route + var excludedSteps = [RouteStep]() + for (index, route) in routes.enumerated() { + let allSteps = route.legs.flatMap { return $0.steps } + let alternateSteps = allSteps.filter { !excludedSteps.contains($0) } + + excludedSteps.append(contentsOf: alternateSteps) + let visibleAlternateSteps = alternateSteps.filter { $0.intersects(visibleBoundingBox) } + + var coordinate: CLLocationCoordinate2D? + + // Obtain a polyline of the set of steps. We'll look for a good spot along this line to place the annotation. + // We will consider a good spot to be somewhere near the middle of the line, making sure that the coordinate is visible on-screen. + if let continuousLine = visibleAlternateSteps.continuousShape(), continuousLine.coordinates.count > 0 { + coordinate = continuousLine.coordinates[0] + + // Pick a coordinate using some randomness in order to give visual variety. + // Take care to snap that coordinate to one that lays on the original route line. + // If the chosen snapped coordinate is not visible on the screen, then we walk back along the route coordinates looking for one that is. + // If none of the earlier points are on screen then we walk forward along the route coordinates until we find one that is. + if let distance = continuousLine.distance(), let sampleCoordinate = continuousLine.indexedCoordinateFromStart(distance: distance * CLLocationDistance.random(in: 0.3...0.8))?.coordinate, let routeShape = route.shape, let snappedCoordinate = routeShape.closestCoordinate(to: sampleCoordinate) { + var foundOnscreenCoordinate = false + var firstOnscreenCoordinate = snappedCoordinate.coordinate + for indexedCoordinate in routeShape.coordinates.prefix(through: snappedCoordinate.index).reversed() { + if visibleBoundingBox.contains(indexedCoordinate) { + firstOnscreenCoordinate = indexedCoordinate + foundOnscreenCoordinate = true + break + } + } + + if foundOnscreenCoordinate { + // We found a point that is both on the route and on-screen + coordinate = firstOnscreenCoordinate + } else { + // we didn't find a previous point that is on-screen so we'll move forward through the coordinates looking for one + for indexedCoordinate in routeShape.coordinates.suffix(from: snappedCoordinate.index) { + if visibleBoundingBox.contains(indexedCoordinate) { + firstOnscreenCoordinate = indexedCoordinate + break + } + } + coordinate = firstOnscreenCoordinate + } + } + } + + guard let annotationCoordinate = coordinate else { return } + + // form the appropriate text string for the annotation + let labelText = self.annotationLabelForRoute(route, tolls: routesContainTolls) + + // Create the feature for this route annotation. Set the styling attributes that will be used to render the annotation in the style layer. + let point = MGLPointFeature() + point.coordinate = annotationCoordinate + + var tailPosition = randomTailPosition + + // convert our coordinate to screen space so we can make a choice on which side of the coordinate the label ends up on + let unprojectedCoordinate = convert(annotationCoordinate, toPointTo: nil) + + // pick the orientation of the bubble "stem" based on how close to the edge of the screen it is + if tailPosition == .left && unprojectedCoordinate.x > bounds.width * 0.75 { + tailPosition = .right + } else if tailPosition == .right && unprojectedCoordinate.x < bounds.width * 0.25 { + tailPosition = .left + } + + var imageName = tailPosition == .left ? "RouteInfoAnnotationLeftHanded" : "RouteInfoAnnotationRightHanded" + + // the selected route uses the colored annotation image + if index == 0 { + imageName += "-Selected" + } + + // set the feature attributes which will be used in styling the symbol style layer + point.attributes = ["selected": index == 0, "tailPosition": tailPosition.rawValue, "text": labelText, "imageName": imageName, "sortOrder": -index] + + features.append(point) + } + + // add the features to the style + self.addRouteAnnotationSymbolLayer(features: features) + } + + private let annotationLayerIdentifier = "RouteDurationAnnotations" + + private func addRouteAnnotationSymbolLayer(features: [MGLPointFeature]) { + guard let style = style else { return } + let dataSource: MGLShapeSource + if let source = style.source(withIdentifier: annotationLayerIdentifier + "-source") as? MGLShapeSource { + dataSource = source + } else { + dataSource = MGLShapeSource(identifier: annotationLayerIdentifier + "-source", features: features, options: nil) + style.addSource(dataSource) + } + + let shapeLayer: MGLSymbolStyleLayer + + if let layer = style.layer(withIdentifier: annotationLayerIdentifier + "-shape") as? MGLSymbolStyleLayer { + shapeLayer = layer + } else { + shapeLayer = MGLSymbolStyleLayer(identifier: annotationLayerIdentifier + "-shape", source: dataSource) + } + + shapeLayer.text = NSExpression(forKeyPath: "text") + let fontSizeByZoomLevel = [ + 13: NSExpression(forConstantValue: 16), + 15.5: NSExpression(forConstantValue: 20) + ] + shapeLayer.textFontSize = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", fontSizeByZoomLevel) + + shapeLayer.textColor = NSExpression(forConditional: NSPredicate(format: "selected == true"), + trueExpression: NSExpression(forConstantValue: UIColor.white), + falseExpression: NSExpression(forConstantValue: UIColor.black)) + + shapeLayer.textFontNames = NSExpression(forConstantValue: ["DIN Pro Medium"]) + shapeLayer.textAllowsOverlap = NSExpression(forConstantValue: true) + shapeLayer.textJustification = NSExpression(forConstantValue: "left") + shapeLayer.symbolZOrder = NSExpression(forConstantValue: NSValue(mglSymbolZOrder: MGLSymbolZOrder.auto)) + shapeLayer.symbolSortKey = NSExpression(forConditional: NSPredicate(format: "selected == true"), + trueExpression: NSExpression(forConstantValue: 1), + falseExpression: NSExpression(format: "sortOrder")) + shapeLayer.iconAnchor = NSExpression(forConditional: NSPredicate(format: "tailPosition == 0"), + trueExpression: NSExpression(forConstantValue: "bottom-left"), + falseExpression: NSExpression(forConstantValue: "bottom-right")) + shapeLayer.textAnchor = shapeLayer.iconAnchor + shapeLayer.iconTextFit = NSExpression(forConstantValue: "both") + + shapeLayer.iconImageName = NSExpression(forKeyPath: "imageName") + shapeLayer.iconOffset = NSExpression(forConditional: NSPredicate(format: "tailPosition == 0"), + trueExpression: NSExpression(forConstantValue: CGVector(dx: 0.5, dy: -1.0)), + falseExpression: NSExpression(forConstantValue: CGVector(dx: -0.5, dy: -1.0))) + shapeLayer.textOffset = shapeLayer.iconOffset + shapeLayer.iconAllowsOverlap = NSExpression(forConstantValue: true) + + style.addLayer(shapeLayer) + } + + public func removeRouteDurationAnnotations() { + guard let style = style else { return } + removeRouteDurationAnnotationsLayerFromStyle(style) + } + + private func removeRouteDurationAnnotationsLayerFromStyle(_ style: MGLStyle) { + if let annotationsLayer = style.layer(withIdentifier: annotationLayerIdentifier + "-shape") { + style.removeLayer(annotationsLayer) + } + + if let annotationsSource = style.source(withIdentifier: annotationLayerIdentifier + "-source") { + style.removeSource(annotationsSource) + } + } + + // This function generates the text for the label to be shown on screen. It will include estimated duration and info on Tolls, if applicable + private func annotationLabelForRoute(_ route: Route, tolls: Bool) -> String { + var eta = DateComponentsFormatter.shortDateComponentsFormatter.string(from: route.expectedTravelTime) ?? "" + + let hasTolls = (route.tollIntersections?.count ?? 0) > 0 + if hasTolls { + eta += "\n" + NSLocalizedString("ROUTE_HAS_TOLLS", value: "Tolls", comment: "This route does have tolls") + if let symbol = Locale.current.currencySymbol { + eta += " " + symbol + } + } else if tolls { + // If one of the routes has tolls, but this one does not then it needs to explicitly say that it has no tolls + // If no routes have tolls at all then we can omit this portion of the string. + eta += "\n" + NSLocalizedString("ROUTE_HAS_NO_TOLLS", value: "No Tolls", comment: "This route does not have tolls") + } + + return eta + } // MARK: Utility Methods @@ -1457,3 +1679,8 @@ extension NavigationMapView { highlightedBuildingsLayer.fillExtrusionOpacity = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", opacityStops) } } + +private enum RouteDurationAnnotationTailPosition: Int { + case left + case right +} diff --git a/Sources/MapboxNavigation/Resources/Base.lproj/Localizable.strings b/Sources/MapboxNavigation/Resources/Base.lproj/Localizable.strings index 7d51c9b08ca..291dcfde4a0 100644 --- a/Sources/MapboxNavigation/Resources/Base.lproj/Localizable.strings +++ b/Sources/MapboxNavigation/Resources/Base.lproj/Localizable.strings @@ -166,6 +166,12 @@ /* Specific route feedback that a road closure type was encountered but not listed as a choice. */ "ROAD_CLOSURE_OTHER_FEEDBACK" = "Other"; +/* This route does not have tolls */ +"ROUTE_HAS_NO_TOLLS" = "No Tolls"; + +/* This route does have tolls */ +"ROUTE_HAS_TOLLS" = "Tolls"; + /* Specific route feedback that user was offered an alternative that was unexpected. */ "ROUTE_QUALITY_ALTERNATIVE_ROUTE_NOT_EXPECTED_FEEDBACK" = "Alternative route not expected"; diff --git a/Sources/MapboxNavigation/Route.swift b/Sources/MapboxNavigation/Route.swift index c30e284c7f2..77ccc004784 100644 --- a/Sources/MapboxNavigation/Route.swift +++ b/Sources/MapboxNavigation/Route.swift @@ -41,4 +41,62 @@ extension Route { return LineString(trimmedPrecedingCoordinates + followingPolyline.trimmed(from: followingPolyline.coordinates[0], distance: distance)!.coordinates.suffix(from: 1)) } } + + var tollIntersections: [Intersection]? { + let allSteps = legs.flatMap { return $0.steps } + + let allIntersections = allSteps.compactMap { return $0.intersections }.reduce([], +) + let intersectionsWithTolls = allIntersections.filter { return $0.tollCollection != nil } + + return intersectionsWithTolls + } + + // returns the list of line segments along the route that fall within given bounding box. Returns nil if there are none. Line segments are defined by the route shape coordinates that lay within the bounding box + func shapes(within: Turf.BoundingBox) -> [LineString]? { + guard let coordinates = shape?.coordinates else { return nil } + var lines = [[CLLocationCoordinate2D]]() + var currentLine: [CLLocationCoordinate2D]? + for coordinate in coordinates { + // see if this coordinate lays within the bounds + if within.contains(coordinate) { + // if there is no current line segment then start one + if currentLine == nil { + currentLine = [CLLocationCoordinate2D]() + } + + // append the coordinate to the current line segment + currentLine?.append(coordinate) + } else { + // if there is a current line segment being built then finish it off and reset + if let currentLine = currentLine { + lines.append(currentLine) + } + currentLine = nil + } + } + + // append any outstanding final segment + if let currentLine = currentLine { + lines.append(currentLine) + } + currentLine = nil + + // return the segments as LineStrings + return lines.compactMap { coordinateList -> LineString? in + return LineString(coordinateList) + } + } +} + +extension RouteStep { + func intersects(_ boundingBox: Turf.BoundingBox) -> Bool { + guard let coordinates = shape?.coordinates else { return false } + + for coordinate in coordinates { + if boundingBox.contains(coordinate) { + return true + } + } + return false + } } diff --git a/Sources/MapboxNavigation/UIImage.swift b/Sources/MapboxNavigation/UIImage.swift index 4ae3f13ba85..d121d0aa120 100644 --- a/Sources/MapboxNavigation/UIImage.swift +++ b/Sources/MapboxNavigation/UIImage.swift @@ -43,4 +43,29 @@ extension UIImage { return scaledImage } + + func tint(_ tintColor: UIColor) -> UIImage { + let imageSize = size + let imageScale = scale + let contextBounds = CGRect(origin: .zero, size: imageSize) + + UIGraphicsBeginImageContextWithOptions(imageSize, false, imageScale) + + defer { UIGraphicsEndImageContext() } + + UIColor.black.setFill() + UIRectFill(contextBounds) + draw(at: .zero) + + guard let imageOverBlack = UIGraphicsGetImageFromCurrentImageContext() else { return self } + tintColor.setFill() + UIRectFill(contextBounds) + + imageOverBlack.draw(at: .zero, blendMode: .multiply, alpha: 1) + draw(at: .zero, blendMode: .destinationIn, alpha: 1) + + guard let finalImage = UIGraphicsGetImageFromCurrentImageContext() else { return self } + + return finalImage + } } From 38cdb1a522590075d6dff6a12944b339ae0e0ba3 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Wed, 20 Jan 2021 15:58:44 -0800 Subject: [PATCH 19/48] Updates for review feedback. Expose more styling properties of the Route Duration Annotations via the Style mechanism. Add more documentation. --- Sources/MapboxNavigation/Array.swift | 15 +++-- Sources/MapboxNavigation/DayStyle.swift | 13 +++- .../MapboxNavigation/NavigationMapView.swift | 67 +++++++++++++------ 3 files changed, 66 insertions(+), 29 deletions(-) diff --git a/Sources/MapboxNavigation/Array.swift b/Sources/MapboxNavigation/Array.swift index ebaa7cd74d0..6736677d8ed 100644 --- a/Sources/MapboxNavigation/Array.swift +++ b/Sources/MapboxNavigation/Array.swift @@ -43,14 +43,17 @@ extension Array where Element == RouteStep { func continuousShape(tolerance: CLLocationDistance = 100) -> LineString? { guard count > 0 else { return nil } guard count > 1 else { return self[0].shape } - var continuousLine = [CLLocationCoordinate2D]() - for index in 0...count-2 { - if let currentStepFinalCoordinate = self[index].shape?.coordinates.last, currentStepFinalCoordinate.distance(to: self[index+1].maneuverLocation) < tolerance, let coordinates = self[index].shape?.coordinates { - continuousLine.append(contentsOf: coordinates) - } + let filteredStepShapes = zip(compactMap { $0.shape }, suffix(from: 1).compactMap { $0.shape }).filter({ + guard let maneuverLocation = $1.coordinates.first else { return false } + + return $0.coordinates.last?.distance(to: maneuverLocation) ?? Double.greatestFiniteMagnitude < tolerance + }) + + let coordinates = filteredStepShapes.flatMap { (firstLine, secondLine) -> [CLLocationCoordinate2D] in + return firstLine.coordinates } - return LineString(continuousLine) + return LineString(coordinates) } } diff --git a/Sources/MapboxNavigation/DayStyle.swift b/Sources/MapboxNavigation/DayStyle.swift index c8e1a19c680..b0fdb9f5470 100644 --- a/Sources/MapboxNavigation/DayStyle.swift +++ b/Sources/MapboxNavigation/DayStyle.swift @@ -31,7 +31,12 @@ extension UIColor { class var defaultBuildingColor: UIColor { get { return #colorLiteral(red: 0.9833194452, green: 0.9843137255, blue: 0.9331936657, alpha: 0.8019049658) } } class var defaultBuildingHighlightColor: UIColor { get { return #colorLiteral(red: 0.337254902, green: 0.6588235294, blue: 0.9843137255, alpha: 0.949406036) } } - class var defaultRouteDurationAnnotationSelectedColor: UIColor { get { return #colorLiteral(red: 0.337254902, green: 0.6588235294, blue: 0.9843137255, alpha: 1) } } + class var routeDurationAnnotationColor: UIColor { get { return #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) } } + class var selectedRouteDurationAnnotationColor: UIColor { get { return #colorLiteral(red: 0.337254902, green: 0.6588235294, blue: 0.9843137255, alpha: 1) } } + + class var routeDurationAnnotationTextColor: UIColor { get { return #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) } } + class var selectedRouteDurationAnnotationTextColor: UIColor { get { return #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) } } + } extension UIColor { @@ -155,7 +160,11 @@ open class DayStyle: Style { NavigationMapView.appearance().trafficUnknownColor = .trafficUnknown NavigationMapView.appearance().buildingDefaultColor = .defaultBuildingColor NavigationMapView.appearance().buildingHighlightColor = .defaultBuildingHighlightColor - NavigationMapView.appearance().routeDurationAnnotationSelectedColor = .defaultRouteDurationAnnotationSelectedColor + NavigationMapView.appearance().routeDurationAnnotationColor = .routeDurationAnnotationColor + NavigationMapView.appearance().routeDurationAnnotationSelectedColor = .selectedRouteDurationAnnotationColor + NavigationMapView.appearance().routeDurationAnnotationFontName = "DIN Pro Medium" + NavigationMapView.appearance().routeDurationAnnotationTextColor = #colorLiteral(red: 0.09803921569, green: 0.09803921569, blue: 0.09803921569, alpha: 1) + NavigationMapView.appearance().routeDurationAnnotationSelectedTextColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) NavigationView.appearance().backgroundColor = #colorLiteral(red: 0.764706, green: 0.752941, blue: 0.733333, alpha: 1) NextBannerView.appearance().backgroundColor = #colorLiteral(red: 0.9675388083, green: 0.9675388083, blue: 0.9675388083, alpha: 1) NextBannerView.appearance(whenContainedInInstancesOf:[InstructionsCardContainerView.self]).backgroundColor = #colorLiteral(red: 0.9675388083, green: 0.9675388083, blue: 0.9675388083, alpha: 1) diff --git a/Sources/MapboxNavigation/NavigationMapView.swift b/Sources/MapboxNavigation/NavigationMapView.swift index 7353a4ece49..89cf0eb3bd4 100755 --- a/Sources/MapboxNavigation/NavigationMapView.swift +++ b/Sources/MapboxNavigation/NavigationMapView.swift @@ -82,6 +82,7 @@ open class NavigationMapView: MGLMapView, UIGestureRecognizerDelegate { static let instruction = "\(identifierNamespace).instruction" static let buildingExtrusion = "\(identifierNamespace).buildingExtrusion" + static let routeDurationAnnotations = "\(identifierNamespace).RouteDurationAnnotations" } struct StyleLayerIdentifier { @@ -99,6 +100,7 @@ open class NavigationMapView: MGLMapView, UIGestureRecognizerDelegate { static let instructionCircle = "\(identifierNamespace).instructionCircle" static let buildingExtrusion = "\(identifierNamespace).buildingExtrusion" + static let routeDurationAnnotations = "\(identifierNamespace).RouteDurationAnnotations" } enum IdentifierType: Int { @@ -129,8 +131,11 @@ open class NavigationMapView: MGLMapView, UIGestureRecognizerDelegate { } } - @objc dynamic public var routeDurationAnnotationSelectedColor: UIColor = .defaultRouteDurationAnnotationSelectedColor - + @objc dynamic public var routeDurationAnnotationSelectedColor: UIColor = .selectedRouteDurationAnnotationColor + @objc dynamic public var routeDurationAnnotationColor: UIColor = .routeDurationAnnotationColor + @objc dynamic public var routeDurationAnnotationSelectedTextColor: UIColor = .selectedRouteDurationAnnotationTextColor + @objc dynamic public var routeDurationAnnotationTextColor: UIColor = .routeDurationAnnotationTextColor + @objc dynamic public var routeDurationAnnotationFontName: String = "DIN Pro Medium" var userLocationForCourseTracking: CLLocation? var animatesUserLocation: Bool = false var altitude: CLLocationDistance @@ -961,19 +966,27 @@ open class NavigationMapView: MGLMapView, UIGestureRecognizerDelegate { ].compactMap { style.source(withIdentifier: $0) })) } + /** + Shows a callout containing the duration of each route. + Useful as a way to give the user more information when picking between multiple route alternatives. + If the route contains any tolled segments then the callout will specify that as well. + */ public func showRouteDurationAnnotations(_ routes: [Route]?) { - guard let style = style else { return } - updateAnnotationSymbolImages(style) - updateRouteDurationAnnotations(routes, style: style) + guard let visibleRoutes = self.routes, visibleRoutes.count > 0 else { return } + updateAnnotationSymbolImages() + updateRouteDurationAnnotations(routes) } - private func updateAnnotationSymbolImages(_ style: MGLStyle) { - guard style.image(forName: "RouteInfoAnnotationLeftHanded") == nil, style.image(forName: "RouteInfoAnnotationRightHanded") == nil else { return } + /** + Updates the image assets in the map style for the route duration annotations. Useful when the desired callout colors change, such as when transitioning between light and dark mode on iOS 13 and later. + */ + private func updateAnnotationSymbolImages() { + guard let style = style, style.image(forName: "RouteInfoAnnotationLeftHanded") == nil, style.image(forName: "RouteInfoAnnotationRightHanded") == nil else { return } let capInsetHeight = CGFloat(22) let capInsetWidth = CGFloat(11) let capInsets = UIEdgeInsets(top: capInsetHeight, left: capInsetWidth, bottom: capInsetHeight, right: capInsetWidth) if let image = Bundle.mapboxNavigation.image(named: "RouteInfoAnnotationLeftHanded") { - let regularRouteImage = image.tint(UIColor.white).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) + let regularRouteImage = image.tint(routeDurationAnnotationColor).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) style.setImage(regularRouteImage, forName: "RouteInfoAnnotationLeftHanded") let selectedRouteImage = image.tint(routeDurationAnnotationSelectedColor).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) @@ -981,7 +994,7 @@ open class NavigationMapView: MGLMapView, UIGestureRecognizerDelegate { } if let image = Bundle.mapboxNavigation.image(named: "RouteInfoAnnotationRightHanded") { - let regularRouteImage = image.tint(UIColor.white).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) + let regularRouteImage = image.tint(routeDurationAnnotationColor).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) style.setImage(regularRouteImage, forName: "RouteInfoAnnotationRightHanded") let selectedRouteImage = image.tint(routeDurationAnnotationSelectedColor).resizableImage(withCapInsets: capInsets, resizingMode: .stretch) @@ -989,7 +1002,12 @@ open class NavigationMapView: MGLMapView, UIGestureRecognizerDelegate { } } - private func updateRouteDurationAnnotations(_ routes: [Route]?, style: MGLStyle) { + /** + Remove any old route duration callouts and generate new ones for each passed in route. + */ + private func updateRouteDurationAnnotations(_ routes: [Route]?) { + guard let style = style else { return } + // remove any existing route annotation removeRouteDurationAnnotationsLayerFromStyle(style) @@ -1093,24 +1111,25 @@ open class NavigationMapView: MGLMapView, UIGestureRecognizerDelegate { self.addRouteAnnotationSymbolLayer(features: features) } - private let annotationLayerIdentifier = "RouteDurationAnnotations" - + /** + Add the MGLSymbolStyleLayer for the route duration annotations. + */ private func addRouteAnnotationSymbolLayer(features: [MGLPointFeature]) { guard let style = style else { return } let dataSource: MGLShapeSource - if let source = style.source(withIdentifier: annotationLayerIdentifier + "-source") as? MGLShapeSource { + if let source = style.source(withIdentifier: SourceIdentifier.routeDurationAnnotations) as? MGLShapeSource { dataSource = source } else { - dataSource = MGLShapeSource(identifier: annotationLayerIdentifier + "-source", features: features, options: nil) + dataSource = MGLShapeSource(identifier: SourceIdentifier.routeDurationAnnotations, features: features, options: nil) style.addSource(dataSource) } let shapeLayer: MGLSymbolStyleLayer - if let layer = style.layer(withIdentifier: annotationLayerIdentifier + "-shape") as? MGLSymbolStyleLayer { + if let layer = style.layer(withIdentifier: StyleLayerIdentifier.routeDurationAnnotations) as? MGLSymbolStyleLayer { shapeLayer = layer } else { - shapeLayer = MGLSymbolStyleLayer(identifier: annotationLayerIdentifier + "-shape", source: dataSource) + shapeLayer = MGLSymbolStyleLayer(identifier: StyleLayerIdentifier.routeDurationAnnotations, source: dataSource) } shapeLayer.text = NSExpression(forKeyPath: "text") @@ -1121,10 +1140,10 @@ open class NavigationMapView: MGLMapView, UIGestureRecognizerDelegate { shapeLayer.textFontSize = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", fontSizeByZoomLevel) shapeLayer.textColor = NSExpression(forConditional: NSPredicate(format: "selected == true"), - trueExpression: NSExpression(forConstantValue: UIColor.white), - falseExpression: NSExpression(forConstantValue: UIColor.black)) + trueExpression: NSExpression(forConstantValue: routeDurationAnnotationSelectedTextColor), + falseExpression: NSExpression(forConstantValue: routeDurationAnnotationTextColor)) - shapeLayer.textFontNames = NSExpression(forConstantValue: ["DIN Pro Medium"]) + shapeLayer.textFontNames = NSExpression(forConstantValue: [self.routeDurationAnnotationFontName]) shapeLayer.textAllowsOverlap = NSExpression(forConstantValue: true) shapeLayer.textJustification = NSExpression(forConstantValue: "left") shapeLayer.symbolZOrder = NSExpression(forConstantValue: NSValue(mglSymbolZOrder: MGLSymbolZOrder.auto)) @@ -1147,17 +1166,23 @@ open class NavigationMapView: MGLMapView, UIGestureRecognizerDelegate { style.addLayer(shapeLayer) } + /** + Removes all visible route duration callouts. + */ public func removeRouteDurationAnnotations() { guard let style = style else { return } removeRouteDurationAnnotationsLayerFromStyle(style) } + /** + Remove the underlying style layers and data sources for the route duration annotations. + */ private func removeRouteDurationAnnotationsLayerFromStyle(_ style: MGLStyle) { - if let annotationsLayer = style.layer(withIdentifier: annotationLayerIdentifier + "-shape") { + if let annotationsLayer = style.layer(withIdentifier: StyleLayerIdentifier.routeDurationAnnotations) { style.removeLayer(annotationsLayer) } - if let annotationsSource = style.source(withIdentifier: annotationLayerIdentifier + "-source") { + if let annotationsSource = style.source(withIdentifier: SourceIdentifier.routeDurationAnnotations) { style.removeSource(annotationsSource) } } From ecf2657a715bff88ef0816210dd1bd3f3989c78a Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Mon, 1 Feb 2021 10:07:13 -0800 Subject: [PATCH 20/48] Move enum to top of file. Remove redundant, unused enum definition. --- Example/ViewController.swift | 5 ----- Sources/MapboxNavigation/NavigationMapView.swift | 10 +++++----- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Example/ViewController.swift b/Example/ViewController.swift index de20cb4c8a9..2f4ec46f7d2 100755 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -7,11 +7,6 @@ import Turf private typealias RouteRequestSuccess = ((RouteResponse) -> Void) private typealias RouteRequestFailure = ((Error) -> Void) -private enum RouteDurationAnnotationTailPosition: Int { - case left - case right -} - class ViewController: UIViewController { // MARK: - IBOutlets @IBOutlet weak var longPressHintView: UIView! diff --git a/Sources/MapboxNavigation/NavigationMapView.swift b/Sources/MapboxNavigation/NavigationMapView.swift index 89cf0eb3bd4..b6eb3e82fce 100755 --- a/Sources/MapboxNavigation/NavigationMapView.swift +++ b/Sources/MapboxNavigation/NavigationMapView.swift @@ -6,6 +6,11 @@ import Turf let identifierNamespace = Bundle.mapboxNavigation.bundleIdentifier ?? "" +private enum RouteDurationAnnotationTailPosition: Int { + case left + case right +} + /** `NavigationMapView` is a subclass of `MGLMapView` with convenience functions for adding `Route` lines to a map. */ @@ -1704,8 +1709,3 @@ extension NavigationMapView { highlightedBuildingsLayer.fillExtrusionOpacity = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", opacityStops) } } - -private enum RouteDurationAnnotationTailPosition: Int { - case left - case right -} From 97f0fa60ccc49a3411ab25a42f0d620fcac5bfe0 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Mon, 1 Feb 2021 10:09:07 -0800 Subject: [PATCH 21/48] Move added Turf and DateComponentsFormatter extension source files to proper location within the project. --- .../DateComponentsFormatter+MGLNavigationAdditions.swift | 0 {MapboxNavigation => Sources/MapboxNavigation}/Turf.swift | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {MapboxNavigation => Sources/MapboxNavigation}/DateComponentsFormatter+MGLNavigationAdditions.swift (100%) rename {MapboxNavigation => Sources/MapboxNavigation}/Turf.swift (100%) diff --git a/MapboxNavigation/DateComponentsFormatter+MGLNavigationAdditions.swift b/Sources/MapboxNavigation/DateComponentsFormatter+MGLNavigationAdditions.swift similarity index 100% rename from MapboxNavigation/DateComponentsFormatter+MGLNavigationAdditions.swift rename to Sources/MapboxNavigation/DateComponentsFormatter+MGLNavigationAdditions.swift diff --git a/MapboxNavigation/Turf.swift b/Sources/MapboxNavigation/Turf.swift similarity index 100% rename from MapboxNavigation/Turf.swift rename to Sources/MapboxNavigation/Turf.swift From a9cfb8e270240b2d028e8503cc6c35084e41b865 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Fri, 5 Mar 2021 10:09:54 -0800 Subject: [PATCH 22/48] Update based on review feedback. Add new methods to CHANGELOG.md. Rename some methods according to style guidelines. Ensure new image resources go into the proper SPM locations. --- CHANGELOG.md | 10 ++++++ Example/ViewController.swift | 8 ++--- Sources/MapboxNavigation/Array.swift | 5 +-- .../MapboxNavigation/NavigationMapView.swift | 8 ++--- .../Resources/Assets.xcassets/Contents.json | 6 ++-- .../RouteAnnotations/Contents.json | 6 ++++ .../Contents.json | 15 +++++++++ .../LeftHandUpDown.pdf | Bin 0 -> 5380 bytes .../Contents.json | 29 ++++++++++++++++++ .../RightHandUpDown.pdf | Bin 0 -> 5375 bytes 10 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/Contents.json create mode 100644 Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/Contents.json create mode 100644 Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/LeftHandUpDown.pdf create mode 100644 Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/Contents.json create mode 100644 Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/RightHandUpDown.pdf diff --git a/CHANGELOG.md b/CHANGELOG.md index 51f46db82f6..38fcbaad200 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,16 @@ * `NavigationMapViewDelegate.navigationMapView(_:alternativeRouteCasingStyleLayerWithIdentifier:source:)` to style the casing of alternative route. * Fixed an issue where the route line periodically peeked out from behind the user puck even though `NavigationViewController.routeLineTracksTraversal` was enabled. ([#2737](https://github.com/mapbox/mapbox-navigation-ios/pull/2737)) * Created the `UserHaloCourseView` similar to `UserCourseView` for approximate location on iOS 14 during the navigation to represent user location. Allow the switch between `UserHaloCourseView` and `UserCourseView` when precise mode is changed. ([#2664](https://github.com/mapbox/mapbox-navigation-ios/pull/2664)) +* Added option to show route duration callouts when previewing route alternatives ([#2734](https://github.com/mapbox/mapbox-navigation-ios/pull/2734)): + * `NavigationMapView.showRouteDurations(along routes: [Route]?)` to show duration annotation callouts on the map for the provided Routes. + * `NavigationMapView.removeRouteDurations()` to remove any route duration annotations currently displayed on the map. +* Added convenience methods for visualizing geographic information on a map style as a debugging tool. ([#2734](https://github.com/mapbox/mapbox-navigation-ios/pull/2734)): + * `MGStyle.addDebugCircleLayer(identifier:coordinate:color:)` to add a MGLCircleStyleLayer with the specified coordinate and color. + * `MGStyle.removeDebugCircleLayers()` to remove from the style all MGLCircleStyleLayers added via `MGStyle.addDebugCircleLayer(identifier:coordinate:color:)` + * `MGStyle.addDebugLineLayer(identifier:coordinates:color:)` to add a MGLLineStyleLayer with the specified coordinates and color. + * `MGStyle.removeDebugLineLayers()` to remove from the style all MGLLineStyleLayers added via `MGStyle.addDebugLineLayer(identifier:coordinates:color:)` + * `MGStyle.addDebugPolygonLayer(identifier:coordinates:color:)` to add a MGLFillStyleLayer with the specified coordinates and color. + * `MGStyle.removeDebugLineLayers()` to remove from the style all MGLFillStyleLayers added via `MGStyle.addDebugPolygonLayer(identifier:coordinates:color:)` ### Instruction banners diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 2f4ec46f7d2..c314ba04925 100755 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -49,7 +49,7 @@ class ViewController: UIViewController { startButton.isEnabled = true mapView?.show(routes) mapView?.showWaypoints(on: currentRoute) - mapView?.showRouteDurationAnnotations(routes) + mapView?.showRouteDurations(along: routes) } } @@ -193,7 +193,7 @@ class ViewController: UIViewController { style.removeDebugCircleLayers() } - mapView?.removeRouteDurationAnnotations() + mapView?.removeRouteDurations() mapView?.unhighlightBuildings() mapView?.removeRoutes() mapView?.removeWaypoints() @@ -435,7 +435,7 @@ extension ViewController: MGLMapViewDelegate { mapView.setVisibleCoordinateBounds(MGLPolygon(coordinates: coords, count: UInt(coords.count)).overlayBounds, animated: false) self.mapView?.show(routes) self.mapView?.showWaypoints(on: currentRoute) - self.mapView?.showRouteDurationAnnotations(routes) + self.mapView?.showRouteDurations(along: routes) } } @@ -464,7 +464,7 @@ extension ViewController: MGLMapViewDelegate { func mapView(_ mapView: MGLMapView, regionDidChangeWith reason: MGLCameraChangeReason, animated: Bool) { guard reason != .programmatic, activeNavigationViewController == nil, let routes = response?.routes else { return } - self.mapView?.showRouteDurationAnnotations(routes) + self.mapView?.showRouteDurations(along: routes) } } diff --git a/Sources/MapboxNavigation/Array.swift b/Sources/MapboxNavigation/Array.swift index 6736677d8ed..22bd25c5d55 100644 --- a/Sources/MapboxNavigation/Array.swift +++ b/Sources/MapboxNavigation/Array.swift @@ -44,9 +44,10 @@ extension Array where Element == RouteStep { guard count > 0 else { return nil } guard count > 1 else { return self[0].shape } - let filteredStepShapes = zip(compactMap { $0.shape }, suffix(from: 1).compactMap { $0.shape }).filter({ + let stepShapes = compactMap { $0.shape } + let filteredStepShapes = zip(stepShapes, stepShapes.suffix(from: 1)).filter({ guard let maneuverLocation = $1.coordinates.first else { return false } - + return $0.coordinates.last?.distance(to: maneuverLocation) ?? Double.greatestFiniteMagnitude < tolerance }) diff --git a/Sources/MapboxNavigation/NavigationMapView.swift b/Sources/MapboxNavigation/NavigationMapView.swift index b6eb3e82fce..c371dcde999 100755 --- a/Sources/MapboxNavigation/NavigationMapView.swift +++ b/Sources/MapboxNavigation/NavigationMapView.swift @@ -976,10 +976,10 @@ open class NavigationMapView: MGLMapView, UIGestureRecognizerDelegate { Useful as a way to give the user more information when picking between multiple route alternatives. If the route contains any tolled segments then the callout will specify that as well. */ - public func showRouteDurationAnnotations(_ routes: [Route]?) { + public func showRouteDurations(along routes: [Route]?) { guard let visibleRoutes = self.routes, visibleRoutes.count > 0 else { return } updateAnnotationSymbolImages() - updateRouteDurationAnnotations(routes) + updateRouteDurations(along: visibleRoutes) } /** @@ -1010,7 +1010,7 @@ open class NavigationMapView: MGLMapView, UIGestureRecognizerDelegate { /** Remove any old route duration callouts and generate new ones for each passed in route. */ - private func updateRouteDurationAnnotations(_ routes: [Route]?) { + private func updateRouteDurations(along routes: [Route]?) { guard let style = style else { return } // remove any existing route annotation @@ -1174,7 +1174,7 @@ open class NavigationMapView: MGLMapView, UIGestureRecognizerDelegate { /** Removes all visible route duration callouts. */ - public func removeRouteDurationAnnotations() { + public func removeRouteDurations() { guard let style = style else { return } removeRouteDurationAnnotationsLayerFromStyle(style) } diff --git a/Sources/MapboxNavigation/Resources/Assets.xcassets/Contents.json b/Sources/MapboxNavigation/Resources/Assets.xcassets/Contents.json index da4a164c918..73c00596a7f 100644 --- a/Sources/MapboxNavigation/Resources/Assets.xcassets/Contents.json +++ b/Sources/MapboxNavigation/Resources/Assets.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/Contents.json b/Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/Contents.json b/Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/Contents.json new file mode 100644 index 00000000000..50bc330dd19 --- /dev/null +++ b/Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "LeftHandUpDown.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/LeftHandUpDown.pdf b/Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/LeftHandUpDown.pdf new file mode 100644 index 0000000000000000000000000000000000000000..374d23c3b25dbf228bf686c4e0ab3b46efebfcdd GIT binary patch literal 5380 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 M4?P7{b<`mL1Ezh<#sB~S literal 0 HcmV?d00001 diff --git a/Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/Contents.json b/Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/Contents.json new file mode 100644 index 00000000000..d9628090e07 --- /dev/null +++ b/Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/Contents.json @@ -0,0 +1,29 @@ +{ + "images" : [ + { + "filename" : "RightHandUpDown.pdf", + "idiom" : "universal", + "resizing" : { + "cap-insets" : { + "bottom" : 22, + "left" : 11, + "right" : 11, + "top" : 22 + }, + "center" : { + "height" : 2, + "mode" : "stretch", + "width" : 1 + }, + "mode" : "9-part" + } + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/RightHandUpDown.pdf b/Sources/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/RightHandUpDown.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0f0da450e6080083844cff784f5ee695905fb0af GIT binary patch literal 5375 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 O3|a;%EUc!Z4*efDkI8NT literal 0 HcmV?d00001 From bc099d4f25f49049e5950c29ebae42f4161fdbda Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Fri, 5 Mar 2021 10:11:37 -0800 Subject: [PATCH 23/48] Remove image resources from old non-SPM bundle resources path. --- .../RouteAnnotations/Contents.json | 6 ---- .../Contents.json | 15 --------- .../LeftHandUpDown.pdf | Bin 5380 -> 0 bytes .../Contents.json | 29 ------------------ .../RightHandUpDown.pdf | Bin 5375 -> 0 bytes 5 files changed, 50 deletions(-) delete mode 100644 MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/Contents.json delete mode 100644 MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/Contents.json delete mode 100644 MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/LeftHandUpDown.pdf delete mode 100644 MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/Contents.json delete mode 100644 MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/RightHandUpDown.pdf diff --git a/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/Contents.json b/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/Contents.json deleted file mode 100644 index 73c00596a7f..00000000000 --- a/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/Contents.json b/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/Contents.json deleted file mode 100644 index 50bc330dd19..00000000000 --- a/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "LeftHandUpDown.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true - } -} diff --git a/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/LeftHandUpDown.pdf b/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationLeftHanded.imageset/LeftHandUpDown.pdf deleted file mode 100644 index 374d23c3b25dbf228bf686c4e0ab3b46efebfcdd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5380 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 M4?P7{b<`mL1Ezh<#sB~S diff --git a/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/Contents.json b/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/Contents.json deleted file mode 100644 index d9628090e07..00000000000 --- a/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/Contents.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "images" : [ - { - "filename" : "RightHandUpDown.pdf", - "idiom" : "universal", - "resizing" : { - "cap-insets" : { - "bottom" : 22, - "left" : 11, - "right" : 11, - "top" : 22 - }, - "center" : { - "height" : 2, - "mode" : "stretch", - "width" : 1 - }, - "mode" : "9-part" - } - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true - } -} diff --git a/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/RightHandUpDown.pdf b/MapboxNavigation/Resources/Assets.xcassets/RouteAnnotations/RouteInfoAnnotationRightHanded.imageset/RightHandUpDown.pdf deleted file mode 100644 index 0f0da450e6080083844cff784f5ee695905fb0af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5375 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 O3|a;%EUc!Z4*efDkI8NT From 0f24191d0ea4658cea84bf22f2eb917eb4e2a566 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Fri, 5 Mar 2021 10:51:34 -0800 Subject: [PATCH 24/48] Tweak CHANGELOG entry. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38fcbaad200..eaf15ef3ba4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,7 @@ * Fixed an issue where the route line periodically peeked out from behind the user puck even though `NavigationViewController.routeLineTracksTraversal` was enabled. ([#2737](https://github.com/mapbox/mapbox-navigation-ios/pull/2737)) * Created the `UserHaloCourseView` similar to `UserCourseView` for approximate location on iOS 14 during the navigation to represent user location. Allow the switch between `UserHaloCourseView` and `UserCourseView` when precise mode is changed. ([#2664](https://github.com/mapbox/mapbox-navigation-ios/pull/2664)) * Added option to show route duration callouts when previewing route alternatives ([#2734](https://github.com/mapbox/mapbox-navigation-ios/pull/2734)): - * `NavigationMapView.showRouteDurations(along routes: [Route]?)` to show duration annotation callouts on the map for the provided Routes. + * `NavigationMapView.showRouteDurations(along routes:)` to show duration annotation callouts on the map for the provided routes. * `NavigationMapView.removeRouteDurations()` to remove any route duration annotations currently displayed on the map. * Added convenience methods for visualizing geographic information on a map style as a debugging tool. ([#2734](https://github.com/mapbox/mapbox-navigation-ios/pull/2734)): * `MGStyle.addDebugCircleLayer(identifier:coordinate:color:)` to add a MGLCircleStyleLayer with the specified coordinate and color. From a67e4ab4fc26fca3ffe890560684f0cbd55815bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Wed, 10 Mar 2021 09:16:08 -0800 Subject: [PATCH 25/48] Mention v2.0.0-beta.1 in this readme too --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 30274cf086d..6512457a680 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,9 @@ To install Mapbox Navigation using [CocoaPods](https://cocoapods.org/): # Latest prerelease pod 'MapboxCoreNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v1.3.0-beta.1' pod 'MapboxNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v1.3.0-beta.1' + # Latest prerelease + pod 'MapboxCoreNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v2.0.0-beta.1' + pod 'MapboxNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v2.0.0-beta.1' ``` 1. Run `pod repo update && pod install` and open the resulting Xcode workspace. From fd1daf42b7919c5c9a2274f1058ed329e1112889 Mon Sep 17 00:00:00 2001 From: jill-cardamon <43434254+jill-cardamon@users.noreply.github.com> Date: Thu, 11 Mar 2021 16:02:59 -0500 Subject: [PATCH 26/48] Allow the use of multiple status banners (#2747) * refactored StatusView.swift to allow the use of multiple status banners, added new show/hide methods that allow to show/hide individual statuses, tested new implementation in StatusViewTests.swift --- CHANGELOG.md | 3 +- MapboxNavigation.xcodeproj/project.pbxproj | 6 +- .../NavigationComponent.swift | 14 +- .../NavigationViewController.swift | 45 +++++- Sources/MapboxNavigation/StatusView.swift | 141 +++++++++++++++--- .../TopBannerViewController.swift | 21 ++- .../PodInstall.xcodeproj/project.pbxproj | 34 +++++ .../StatusViewTests.swift | 81 ++++++++++ 8 files changed, 312 insertions(+), 33 deletions(-) create mode 100644 Tests/MapboxNavigationTests/StatusViewTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index eaf15ef3ba4..4e594a85008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,13 @@ ## v1.3.0 * MapboxCoreNavigation can now be installed using Swift Package Manager. ([#2771](https://github.com/mapbox/mapbox-navigation-ios/pull/2771)) -* The CarPlay guidance panel now shows lane guidance. ([#2798](https://github.com/mapbox/mapbox-navigation-ios/pull/2798)) +* The CarPlay guidance panel now shows lane guidance. ([#1885](https://github.com/mapbox/mapbox-navigation-ios/pull/1885)) * Old versions of routing tiles are automatically deleted from the cache to save storage space. ([#2807](https://github.com/mapbox/mapbox-navigation-ios/pull/2807)) * Fixed an issue where lane guidance icons would indicate the wrong arrow for certain maneuvers. ([#2796](https://github.com/mapbox/mapbox-navigation-ios/pull/2796), [#2809](https://github.com/mapbox/mapbox-navigation-ios/pull/2809)) * Fixed a crash showing a junction view. ([#2805](https://github.com/mapbox/mapbox-navigation-ios/pull/2805)) * Fixed an issue with CarPlay visual instructions where U-Turn maneuver icons were not being flipped properly based on regional driving side ([#2803](https://github.com/mapbox/mapbox-navigation-ios/pull/2803)) * Fixed swiping for right-to-left languages for the Guidance Card UI to be more intuitive. ([#2724](https://github.com/mapbox/mapbox-navigation-ios/pull/2724)) +* `NavigationViewController` can now manage multiple status banners one after another. Renamed the `NavigationViewController.showStatus(title:spinner:duration:animated:interactive:)` method to `NavigationViewController.show(_:)` and added a corresponding `NavigationViewController.hide(_:)` method. Renamed the `NavigationStatusPresenter.showStatus(title:spinner:duration:animated:interactive:)` method to `NavigationStatusPresenter.show(_:)` and added a `NavigationStatusPresenter.hide(_:)` method. ([#2747](https://github.com/mapbox/mapbox-navigation-ios/pull/2747)) ## v1.2.1 diff --git a/MapboxNavigation.xcodeproj/project.pbxproj b/MapboxNavigation.xcodeproj/project.pbxproj index addd644d76a..51682832d2c 100644 --- a/MapboxNavigation.xcodeproj/project.pbxproj +++ b/MapboxNavigation.xcodeproj/project.pbxproj @@ -302,6 +302,7 @@ B430D2FA25534FDC0088CC23 /* UserHaloCourseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B430D2F925534FDC0088CC23 /* UserHaloCourseView.swift */; }; B48CEFC125796FD300696BB3 /* route-for-vanishing-route-line.json in Resources */ = {isa = PBXBuildFile; fileRef = B48CEFAB25796F5A00696BB3 /* route-for-vanishing-route-line.json */; }; B4F7631C2578751300177B33 /* VanishingRouteLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4F7631B2578751300177B33 /* VanishingRouteLine.swift */; }; + C3D225512587F411007DBCDF /* StatusViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D225502587F411007DBCDF /* StatusViewTests.swift */; }; C51511D120EAC89D00372A91 /* CPMapTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C51511D020EAC89D00372A91 /* CPMapTemplate.swift */; }; C51DF8661F38C31C006C6A15 /* Locale.swift in Sources */ = {isa = PBXBuildFile; fileRef = C51DF8651F38C31C006C6A15 /* Locale.swift */; }; C51FC31720F689F800400CE7 /* CustomStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = C51FC31620F689F800400CE7 /* CustomStyles.swift */; }; @@ -884,9 +885,10 @@ AED2156E208F7FEA009AA673 /* NavigationViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewControllerTests.swift; sourceTree = ""; }; AED6285522CBE4CE00058A51 /* ViewController+GuidanceCards.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "ViewController+GuidanceCards.swift"; path = "Example/ViewController+GuidanceCards.swift"; sourceTree = ""; }; AEF2C8F12072B603007B061F /* routeWithTunnels_9thStreetDC.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = routeWithTunnels_9thStreetDC.json; sourceTree = ""; }; - B430D2F925534FDC0088CC23 /* UserHaloCourseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserHaloCourseView.swift; sourceTree = ""; }; + B430D2F925534FDC0088CC23 /* UserHaloCourseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserHaloCourseView.swift; sourceTree = ""; }; B48CEFAB25796F5A00696BB3 /* route-for-vanishing-route-line.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "route-for-vanishing-route-line.json"; sourceTree = ""; }; B4F7631B2578751300177B33 /* VanishingRouteLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VanishingRouteLine.swift; sourceTree = ""; }; + C3D225502587F411007DBCDF /* StatusViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusViewTests.swift; sourceTree = ""; }; C51511D020EAC89D00372A91 /* CPMapTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPMapTemplate.swift; sourceTree = ""; }; C51DF8651F38C31C006C6A15 /* Locale.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Locale.swift; sourceTree = ""; }; C51FC31620F689F800400CE7 /* CustomStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CustomStyles.swift; path = Example/CustomStyles.swift; sourceTree = ""; }; @@ -1369,6 +1371,7 @@ 35B711D01E5E7AD2001EDA8D /* MapboxNavigationTests */ = { isa = PBXGroup; children = ( + C3D225502587F411007DBCDF /* StatusViewTests.swift */, 16E11B53212B3D4700027CD3 /* Extensions and Categories */, 355DB5731EFA73410091BFB7 /* Fixtures */, 3527D2B61EC45FBD00C07FC9 /* Fixtures.xcassets */, @@ -2677,6 +2680,7 @@ 8D9CD7FF20880581004DC4B3 /* XCTestCase.swift in Sources */, 35DC585D1FABC61100B5A956 /* InstructionsBannerViewIntegrationTests.swift in Sources */, 3502231A205BC94E00E1449A /* Constants.swift in Sources */, + C3D225512587F411007DBCDF /* StatusViewTests.swift in Sources */, DA0557252155040700A1F2AA /* RouteTests.swift in Sources */, C55C299920D2E2F600B0406C /* NavigationMapViewTests.swift in Sources */, 4341758423061666004264A9 /* SnapshotTest+Mapbox.swift in Sources */, diff --git a/Sources/MapboxNavigation/NavigationComponent.swift b/Sources/MapboxNavigation/NavigationComponent.swift index ac6356a17bf..2fe6c7b2e66 100644 --- a/Sources/MapboxNavigation/NavigationComponent.swift +++ b/Sources/MapboxNavigation/NavigationComponent.swift @@ -36,8 +36,20 @@ public protocol CarPlayConnectionObserver: class { This protocol defines a UI Component that is capable of presenting a status message. */ public protocol NavigationStatusPresenter: class { + /** + Shows a Status for a specified amount of time. + */ + func show(_: StatusView.Status) + + /** + Hides a given Status without hiding the status view. + */ + func hide(_: StatusView.Status) + /** Shows the status view for a specified amount of time. + `showStatus()` uses a default value for priority and the title input as identifier. To use these variables, use `show(_:)` */ - func showStatus(title: String, spinner: Bool, duration: TimeInterval, animated: Bool, interactive: Bool) + @available(*, deprecated, message: "Add a status using show(_:) instead") + func showStatus(title: String, spinner spin: Bool, duration: TimeInterval, animated: Bool, interactive: Bool) } diff --git a/Sources/MapboxNavigation/NavigationViewController.swift b/Sources/MapboxNavigation/NavigationViewController.swift index 052d920ae45..a02752aac26 100644 --- a/Sources/MapboxNavigation/NavigationViewController.swift +++ b/Sources/MapboxNavigation/NavigationViewController.swift @@ -33,6 +33,7 @@ public enum MapOrnamentPosition { `CarPlayNavigationViewController` manages the corresponding user interface on a CarPlay screen. */ open class NavigationViewController: UIViewController, NavigationStatusPresenter { + /** A `Route` object constructed by [MapboxDirections](https://docs.mapbox.com/ios/api/directions/) along with its index in a `RouteResponse`. @@ -51,6 +52,8 @@ open class NavigationViewController: UIViewController, NavigationStatusPresenter } } + var didChangeAuthorizationIsFirstCalled = true + /** A `Route` object constructed by [MapboxDirections](https://docs.mapbox.com/ios/api/directions/). */ @@ -401,7 +404,10 @@ open class NavigationViewController: UIViewController, NavigationStatusPresenter guard AVAudioSession.sharedInstance().outputVolume <= NavigationViewMinimumVolumeForWarning else { return } let title = NSLocalizedString("INAUDIBLE_INSTRUCTIONS_CTA", bundle: .mapboxNavigation, value: "Adjust Volume to Hear Instructions", comment: "Label indicating the device volume is too low to hear spoken instructions and needs to be manually increased") - showStatus(title: title, spinner: false, duration: 3, animated: true, interactive: false) + + // create low volume notification status and append to array of statuses + let lowVolumeStatus = StatusView.Status(identifier: "INAUDIBLE_INSTRUCTIONS_CTA", title: title, duration: 3, animated: true, priority: 3) + show(lowVolumeStatus) } @@ -462,9 +468,28 @@ open class NavigationViewController: UIViewController, NavigationStatusPresenter UNUserNotificationCenter.current().add(notificationRequest, withCompletionHandler: nil) } - public func showStatus(title: String, spinner: Bool, duration: TimeInterval, animated: Bool, interactive: Bool) { + /** + Shows a Status for a specified amount of time. + */ + public func show(_ status: StatusView.Status) { + navigationComponents.compactMap({ $0 as? NavigationStatusPresenter }).forEach { + $0.show(status) + } + } + + /** + Hides a given Status without hiding the status view. + */ + public func hide(_ status: StatusView.Status) { navigationComponents.compactMap({ $0 as? NavigationStatusPresenter }).forEach { - $0.showStatus(title: title, spinner: spinner, duration: duration, animated: animated, interactive: interactive) + $0.hide(status) + } + } + + @available(*, deprecated, message: "Add a status using show(_:) instead") + public func showStatus(title: String, spinner spin: Bool, duration: TimeInterval, animated: Bool, interactive: Bool) { + navigationComponents.compactMap({ $0 as? NavigationStatusPresenter }).forEach { + $0.showStatus(title: title, spinner: spin, duration: duration, animated: animated, interactive: interactive) } } } @@ -724,21 +749,29 @@ extension NavigationViewController: NavigationServiceDelegate { public func navigationServiceShouldDisableBatteryMonitoring(_ service: NavigationService) -> Bool { return navigationComponents.allSatisfy { $0.navigationServiceShouldDisableBatteryMonitoring(service) } } - + public func navigationServiceDidChangeAuthorization(_ service: NavigationService, didChangeAuthorizationFor locationManager: CLLocationManager) { // CLLocationManager.accuracyAuthorization was introduced in the iOS 14 SDK in Xcode 12, so Xcode 11 doesn’t recognize it. guard let accuracyAuthorizationValue = locationManager.value(forKey: "accuracyAuthorization") as? Int else { return } let accuracyAuthorization = MBNavigationAccuracyAuthorization(rawValue: accuracyAuthorizationValue) + let previousAuthorizationValue = 1 - accuracyAuthorizationValue + + // create authorization status + let title = NSLocalizedString("ENABLE_PRECISE_LOCATION", bundle: .mapboxNavigation, value: "Enable precise location to navigate", comment: "Label indicating precise location is off and needs to be turned on to navigate") + let authorizationStatus = StatusView.Status(identifier: "ENABLE_PRECISE_LOCATION", title: title, duration: .infinity, priority: 1) if #available(iOS 14.0, *), accuracyAuthorization == .reducedAccuracy { - let title = NSLocalizedString("ENABLE_PRECISE_LOCATION", bundle: .mapboxNavigation, value: "Enable precise location to navigate", comment: "Label indicating precise location is off and needs to be turned on to navigate") - showStatus(title: title, spinner: false, duration: 20, animated: true, interactive: false) + show(authorizationStatus) mapView?.reducedAccuracyActivatedMode = true + } else if #available(iOS 14.0, *), previousAuthorizationValue == 1, didChangeAuthorizationIsFirstCalled == false { + hide(authorizationStatus) + mapView?.reducedAccuracyActivatedMode = false } else { //Fallback on earlier versions mapView?.reducedAccuracyActivatedMode = false return } + didChangeAuthorizationIsFirstCalled = false } // MARK: - Building Extrusion Highlighting diff --git a/Sources/MapboxNavigation/StatusView.swift b/Sources/MapboxNavigation/StatusView.swift index aa63995b9be..bbad60eda35 100644 --- a/Sources/MapboxNavigation/StatusView.swift +++ b/Sources/MapboxNavigation/StatusView.swift @@ -46,6 +46,58 @@ public class StatusView: UIControl { } } + var statuses: [Status] = [] + + /** + `Status` is a struct which stores information to be displayed by the `StatusView` + */ + public struct Status { + /** + A string that uniquely identifies the `Status` + */ + public var identifier: String + /** + The text that will appear on the `Status` + */ + public let title: String + /** + A boolean that indicates whether a `spinner` should be shown during animations + set to `false` by default + */ + public var spinner: Bool = false + /** + A TimeInterval that designates the length of time the `Status` will be displayed in seconds + To display the `Status` indefinitely, set `duration` to `.infinity` + */ + public let duration: TimeInterval + /** + A boolean that indicates whether showing and hiding of the `Status` should be animated + set to `true` by default + */ + public var animated: Bool = true + /** + A boolean that indicates whether the `Status` should respond to touch events + set to `false` by default + */ + public var interactive: Bool = false + /** + A typealias which is used to rank a `Status` by importance + A lower `priority` value corresponds to a higher priority + */ + public var priority: Priority + } + + /** + `Priority` is used to display `Status`es by importance + Lower values correspond to higher priority. + */ + public typealias Priority = Int +// — Highest Priority — +// rerouting (value = 0) +// enable precise location (value = 1) +// simulation banner (value = 2) +// — Lowest Priority — + public override init(frame: CGRect) { super.init(frame: frame) commonInit() @@ -84,6 +136,7 @@ public class StatusView: UIControl { let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(StatusView.tap(_:))) addGestureRecognizer(tapRecognizer) + } @objc func pan(_ sender: UIPanGestureRecognizer) { @@ -117,39 +170,79 @@ public class StatusView: UIControl { } } + /** + Shows the status view for a specified amount of time. + `showStatus()` uses a default value for priority and the title input as identifier. To use these variables, use `show(_:)` + */ + @available(*, deprecated, message: "Add a status using show(_:) instead") public func showStatus(title: String, spinner spin: Bool = false, duration: TimeInterval, animated: Bool = true, interactive: Bool = false) { - show(title, showSpinner: spin, interactive: interactive) - guard duration < .infinity else { return } - hide(delay: duration, animated: animated) + let status = Status(identifier: title, title: title, spinner: spin, duration: duration, animated: animated, interactive: interactive, priority: 1) + show(status) + } + + /** + Adds a new status to statuses array. + */ + func show(_ status: Status) { + if let row = statuses.firstIndex(where: {$0.identifier.contains(status.identifier)}) { + statuses[row] = status + } else { + statuses.append(status) + } + manageStatuses() + } + + /** + Manages showing and hiding Statuses and the status view itself. + */ + func manageStatuses(status: Status? = nil) { + if statuses.isEmpty { + hide(delay: status?.duration ?? 0, animated: status?.animated ?? true) + } else { + // if we hide a Status and there are Statuses left in the statuses array, show the Status with highest priority + guard let highestPriorityStatus = statuses.min(by: {$0.priority < $1.priority}) else { return } + show(status: highestPriorityStatus) + hide(with: highestPriorityStatus, delay: highestPriorityStatus.duration) + } + } + + /** + Hides a given Status without hiding the status view. + */ + func hide(_ status: Status?) { + guard let row = statuses.firstIndex(where: {$0.identifier == status?.identifier}) else { return } + let removedStatus = statuses.remove(at: row) + manageStatuses(status: removedStatus) } func showSimulationStatus(speed: Int) { let format = NSLocalizedString("USER_IN_SIMULATION_MODE", bundle: .mapboxNavigation, value: "Simulating Navigation at %@×", comment: "The text of a banner that appears during turn-by-turn navigation when route simulation is enabled.") let title = String.localizedStringWithFormat(format, NumberFormatter.localizedString(from: speed as NSNumber, number: .decimal)) - showStatus(title: title, duration: .infinity, interactive: true) + let simulationStatus = Status(identifier: "USER_IN_SIMULATION_MODE", title: title, duration: .infinity, interactive: true, priority: 2) + show(simulationStatus) } /** Shows the status view with an optional spinner. */ - public func show(_ title: String, showSpinner: Bool, interactive: Bool = false) { - isEnabled = interactive - textLabel.text = title + public func show(status: Status) { + isEnabled = status.interactive + textLabel.text = status.title activityIndicatorView.hidesWhenStopped = true - if (!showSpinner) { activityIndicatorView.stopAnimating() } + if (!status.spinner) { activityIndicatorView.stopAnimating() } guard !isCurrentlyVisible, isHidden else { return } let show = { self.isHidden = false self.textLabel.alpha = 1 - if (showSpinner) { self.activityIndicatorView.isHidden = false } + if (status.spinner) { self.activityIndicatorView.isHidden = false } self.superview?.layoutIfNeeded() } UIView.defaultAnimation(0.3, animations:show, completion:{ _ in self.isCurrentlyVisible = true - guard showSpinner else { return } + guard status.spinner else { return } self.activityIndicatorView.startAnimating() }) } @@ -157,22 +250,30 @@ public class StatusView: UIControl { /** Hides the status view. */ - public func hide(delay: TimeInterval = 0, animated: Bool = true) { + public func hide(with status: Status? = nil, delay: TimeInterval = 0, animated: Bool = true) { let hide = { - self.isHidden = true - self.textLabel.alpha = 0 - self.activityIndicatorView.isHidden = true + if status == nil { + self.isHidden = true + self.textLabel.alpha = 0 + self.activityIndicatorView.isHidden = true + } else { + self.hide(status) + } } let animate = { let fireTime = DispatchTime.now() + delay DispatchQueue.main.asyncAfter(deadline: fireTime, execute: { - guard !self.isHidden, self.isCurrentlyVisible else { return } - - self.activityIndicatorView.stopAnimating() - UIView.defaultAnimation(0.3, delay: 0, animations: hide, completion: { _ in - self.isCurrentlyVisible = false - }) + if status == nil { + guard !self.isHidden, self.isCurrentlyVisible else { return } + + self.activityIndicatorView.stopAnimating() + UIView.defaultAnimation(0.3, delay: 0, animations: hide, completion: { _ in + self.isCurrentlyVisible = false + }) + } else { + self.hide(status) + } }) } diff --git a/Sources/MapboxNavigation/TopBannerViewController.swift b/Sources/MapboxNavigation/TopBannerViewController.swift index fac4fa6de43..150691b6548 100644 --- a/Sources/MapboxNavigation/TopBannerViewController.swift +++ b/Sources/MapboxNavigation/TopBannerViewController.swift @@ -384,7 +384,8 @@ extension TopBannerViewController: NavigationComponent { public func navigationService(_ service: NavigationService, willRerouteFrom location: CLLocation) { let title = NSLocalizedString("REROUTING", bundle: .mapboxNavigation, value: "Rerouting…", comment: "Indicates that rerouting is in progress") lanesView.hide() - statusView.show(title, showSpinner: true) + let reroutingStatus = StatusView.Status(identifier: "REROUTING", title: title, duration: 20, priority: 0) + show(reroutingStatus) } public func navigationService(_ service: NavigationService, didRerouteAlong route: Route, at location: CLLocation?, proactive: Bool) { @@ -399,7 +400,10 @@ extension TopBannerViewController: NavigationComponent { if (proactive) { let title = NSLocalizedString("FASTER_ROUTE_FOUND", bundle: .mapboxNavigation, value: "Faster Route Found", comment: "Indicates a faster route was found") - statusView.showStatus(title: title, spinner: true, duration: 3) + + // create faster route status and append to array of statuses + let fasterRouteStatus = StatusView.Status(identifier: "FASTER_ROUTE_FOUND", title: title, duration: 3, priority: 0) + statusView.show(fasterRouteStatus) } } @@ -461,8 +465,17 @@ extension TopBannerViewController: CarPlayConnectionObserver { } extension TopBannerViewController: NavigationStatusPresenter { - public func showStatus(title: String, spinner spin: Bool, duration time: TimeInterval, animated: Bool, interactive: Bool) { - statusView.showStatus(title: title, spinner: spin, duration: time, animated: animated, interactive: interactive) + public func show(_ status: StatusView.Status) { + statusView.show(status) + } + + public func hide(_ status: StatusView.Status) { + statusView.hide(status) + } + + @available(*, deprecated, message: "Add a status using show(_:) instead") + public func showStatus(title: String, spinner spin: Bool, duration: TimeInterval, animated: Bool, interactive: Bool) { + statusView.showStatus(title: title, spinner: spin, duration: duration, animated: animated, interactive: interactive) } } diff --git a/Tests/CocoaPodsTest/PodInstall/PodInstall.xcodeproj/project.pbxproj b/Tests/CocoaPodsTest/PodInstall/PodInstall.xcodeproj/project.pbxproj index 15e834775d5..087a3e45ffb 100644 --- a/Tests/CocoaPodsTest/PodInstall/PodInstall.xcodeproj/project.pbxproj +++ b/Tests/CocoaPodsTest/PodInstall/PodInstall.xcodeproj/project.pbxproj @@ -128,6 +128,7 @@ buildConfigurationList = 352544F91E66623D004C8F1C /* Build configuration list for PBXNativeTarget "PodInstall" */; buildPhases = ( FCAEDB6CF8C116409B047EA1 /* [CP] Check Pods Manifest.lock */, + C0CE5D7A671F38C9FBA189FB /* [CP] Prepare Artifacts */, 352544D81E66623D004C8F1C /* Sources */, 352544D91E66623D004C8F1C /* Frameworks */, 352544DA1E66623D004C8F1C /* Resources */, @@ -231,11 +232,19 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-PodInstall/Pods-PodInstall-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/cocoapods-artifacts-${CONFIGURATION}.txt", "${PODS_ROOT}/Mapbox-iOS-SDK/dynamic/Mapbox.framework", + "${PODS_ROOT}/Mapbox-iOS-SDK/dynamic/Mapbox.framework.dSYM", + "${PODS_ROOT}/Mapbox-iOS-SDK/dynamic/8F3E82C3-52DF-39E5-809D-3389B6CBCAD4.bcsymbolmap", + "${PODS_ROOT}/Mapbox-iOS-SDK/dynamic/0DC8DAD8-C6D7-343B-ACBF-79B48068D7CC.bcsymbolmap", + "${PODS_ROOT}/MapboxAccounts/MapboxAccounts.framework", "${BUILT_PRODUCTS_DIR}/MapboxCoreNavigation/MapboxCoreNavigation.framework", "${BUILT_PRODUCTS_DIR}/MapboxDirections/MapboxDirections.framework", "${BUILT_PRODUCTS_DIR}/MapboxMobileEvents/MapboxMobileEvents.framework", "${BUILT_PRODUCTS_DIR}/MapboxNavigation/MapboxNavigation.framework", + "${PODS_ROOT}/MapboxNavigationNative/MapboxNavigationNative.framework", + "${PODS_ROOT}/MapboxNavigationNative/MapboxNavigationNative.framework.dSYM", + "${PODS_ROOT}/MapboxNavigationNative/4868A5C8-3179-3F0D-ABFC-0C941CC5D2E4.bcsymbolmap", "${BUILT_PRODUCTS_DIR}/MapboxSpeech/MapboxSpeech.framework", "${BUILT_PRODUCTS_DIR}/Polyline/Polyline.framework", "${BUILT_PRODUCTS_DIR}/Solar/Solar.framework", @@ -247,10 +256,17 @@ name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Mapbox.framework", + "${DWARF_DSYM_FOLDER_PATH}/Mapbox.framework.dSYM", + "${BUILT_PRODUCTS_DIR}/8F3E82C3-52DF-39E5-809D-3389B6CBCAD4.bcsymbolmap", + "${BUILT_PRODUCTS_DIR}/0DC8DAD8-C6D7-343B-ACBF-79B48068D7CC.bcsymbolmap", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxAccounts.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCoreNavigation.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxDirections.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMobileEvents.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxNavigation.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxNavigationNative.framework", + "${DWARF_DSYM_FOLDER_PATH}/MapboxNavigationNative.framework.dSYM", + "${BUILT_PRODUCTS_DIR}/4868A5C8-3179-3F0D-ABFC-0C941CC5D2E4.bcsymbolmap", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxSpeech.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Polyline.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Solar.framework", @@ -264,6 +280,24 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PodInstall/Pods-PodInstall-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + C0CE5D7A671F38C9FBA189FB /* [CP] Prepare Artifacts */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PodInstall/Pods-PodInstall-artifacts.sh", + "${PODS_ROOT}/MapboxCommon/MapboxCommon.xcframework", + ); + name = "[CP] Prepare Artifacts"; + outputPaths = ( + "${BUILT_PRODUCTS_DIR}/cocoapods-artifacts-${CONFIGURATION}.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PodInstall/Pods-PodInstall-artifacts.sh\"\n"; + showEnvVarsInLog = 0; + }; FCAEDB6CF8C116409B047EA1 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/Tests/MapboxNavigationTests/StatusViewTests.swift b/Tests/MapboxNavigationTests/StatusViewTests.swift new file mode 100644 index 00000000000..6c381ed4c92 --- /dev/null +++ b/Tests/MapboxNavigationTests/StatusViewTests.swift @@ -0,0 +1,81 @@ +import XCTest +import UIKit +@testable import MapboxNavigation +@testable import MapboxCoreNavigation + +class StatusViewTests: XCTestCase { + lazy var statusView: StatusView = { + let view: StatusView = .forAutoLayout() + view.isHidden = true + return view + }() + + func testWithDelayShorterThanDuration() { + show(firstStatus()) + XCTAssertEqual(self.statusView.statuses.count, 1) + } + + func testWithDelayLongerThanDuration() { + let seconds = 5.0 + XCTAssertTrue(statusView.isHidden) + show(firstStatus()) + XCTAssertFalse(statusView.isHidden) + let path = #keyPath(UIView.isHidden) + let expectation = XCTKVOExpectation(keyPath: path, object: statusView, expectedValue: true) + self.wait(for: [expectation], timeout: seconds) + XCTAssertTrue(statusView.isHidden) + XCTAssertEqual(self.statusView.statuses.count, 0) + } + + func testFirstAndSecond() { + show(firstStatus()) + XCTAssertFalse(statusView.isHidden) + show(secondStatus()) + XCTAssertEqual(statusView.statuses.count, 2) + let path = #keyPath(UIView.isHidden) + let expectation = XCTKVOExpectation(keyPath: path, object: statusView, expectedValue: true) + self.wait(for: [expectation], timeout: secondStatus().duration + 10.0) + XCTAssertTrue(statusView.isHidden) + XCTAssertEqual(self.statusView.statuses.count, 0) + } + + func testWithInfinite() { + show(firstStatus()) + show(thirdStatus()) + XCTAssertEqual(self.statusView.statuses.count, 2) + let path = #keyPath(UIView.isHidden) + let expectation = XCTKVOExpectation(keyPath: path, object: statusView, expectedValue: true) + XCTWaiter.wait(for: [expectation], timeout: 15.0) + XCTAssertFalse(statusView.isHidden) + XCTAssertEqual(self.statusView.statuses.count, 1) + } +} + +extension StatusViewTests { + + // define statuses + func firstStatus() -> StatusView.Status { + return StatusView.Status(identifier: "FIRST_TEST_STATUS", title: "first test status", duration: 1, priority: 0) + } + + func secondStatus() -> StatusView.Status { + return StatusView.Status(identifier: "SECOND_TEST_STATUS", title: "second test status", duration: 5, priority: 1) + } + + func thirdStatus() -> StatusView.Status { + return StatusView.Status(identifier: "THIRD_TEST_STATUS", title: "third test status", duration: .infinity, priority: 2) + } + + func show(_ status: StatusView.Status) { + statusView.show(status) + } + + func hide(_ status: StatusView.Status) { + statusView.hide(status) + } + + func clearStatuses() { + statusView.statuses.removeAll() + } +} + From 70a31f5c22361e4634207f9e22eb2b43744483d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Fri, 12 Mar 2021 17:27:34 -0800 Subject: [PATCH 27/48] Updated Transifex configuration after SPM refactor --- .tx/config | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.tx/config b/.tx/config index 1a6963888fd..10829d5934e 100644 --- a/.tx/config +++ b/.tx/config @@ -5,19 +5,19 @@ minimum_perc = 80 type = STRINGS [mapbox-navigation-ios.LocalizableStrings] -file_filter = MapboxNavigation/Resources/.lproj/Localizable.strings -source_file = MapboxNavigation/Resources/Base.lproj/Localizable.strings +file_filter = Sources/MapboxNavigation/Resources/.lproj/Localizable.strings +source_file = Sources/MapboxNavigation/Resources/Base.lproj/Localizable.strings source_lang = en [mapbox-navigation-ios.localizablestringsdict] -file_filter = MapboxNavigation/Resources/.lproj/Localizable.stringsdict -source_file = MapboxNavigation/Resources/en.lproj/Localizable.stringsdict +file_filter = Sources/MapboxNavigation/Resources/.lproj/Localizable.stringsdict +source_file = Sources/MapboxNavigation/Resources/en.lproj/Localizable.stringsdict source_lang = en type = STRINGSDICT [mapbox-navigation-ios.LocalizableCoreStrings] -file_filter = MapboxCoreNavigation/Resources/.lproj/Localizable.strings -source_file = MapboxCoreNavigation/Resources/Base.lproj/Localizable.strings +file_filter = Sources/MapboxCoreNavigation/Resources/.lproj/Localizable.strings +source_file = Sources/MapboxCoreNavigation/Resources/Base.lproj/Localizable.strings source_lang = en [mapbox-navigation-ios.ExampleMainStrings] @@ -26,6 +26,6 @@ source_file = Example/en.lproj/Main.strings source_lang = en [mapbox-navigation-ios.navigationstrings] -file_filter = MapboxNavigation/Resources/.lproj/Navigation.strings -source_file = MapboxNavigation/Resources/Base.lproj/Navigation.strings +file_filter = Sources/MapboxNavigation/Resources/.lproj/Navigation.strings +source_file = Sources/MapboxNavigation/Resources/Base.lproj/Navigation.strings source_lang = en From 76a6a65bcd440eab4b8e10bf9ee7b59e73c968ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Fri, 12 Mar 2021 17:31:21 -0800 Subject: [PATCH 28/48] Added Catalan stringsdict --- MapboxNavigation.xcodeproj/project.pbxproj | 4 +- .../ca.lproj/Localizable.stringsdict | 54 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 Sources/MapboxNavigation/Resources/ca.lproj/Localizable.stringsdict diff --git a/MapboxNavigation.xcodeproj/project.pbxproj b/MapboxNavigation.xcodeproj/project.pbxproj index 51682832d2c..aa5182fe476 100644 --- a/MapboxNavigation.xcodeproj/project.pbxproj +++ b/MapboxNavigation.xcodeproj/project.pbxproj @@ -885,7 +885,7 @@ AED2156E208F7FEA009AA673 /* NavigationViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewControllerTests.swift; sourceTree = ""; }; AED6285522CBE4CE00058A51 /* ViewController+GuidanceCards.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "ViewController+GuidanceCards.swift"; path = "Example/ViewController+GuidanceCards.swift"; sourceTree = ""; }; AEF2C8F12072B603007B061F /* routeWithTunnels_9thStreetDC.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = routeWithTunnels_9thStreetDC.json; sourceTree = ""; }; - B430D2F925534FDC0088CC23 /* UserHaloCourseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserHaloCourseView.swift; sourceTree = ""; }; + B430D2F925534FDC0088CC23 /* UserHaloCourseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserHaloCourseView.swift; sourceTree = ""; }; B48CEFAB25796F5A00696BB3 /* route-for-vanishing-route-line.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "route-for-vanishing-route-line.json"; sourceTree = ""; }; B4F7631B2578751300177B33 /* VanishingRouteLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VanishingRouteLine.swift; sourceTree = ""; }; C3D225502587F411007DBCDF /* StatusViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusViewTests.swift; sourceTree = ""; }; @@ -948,6 +948,7 @@ DA0557242155040700A1F2AA /* RouteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteTests.swift; sourceTree = ""; }; DA0A4B7724D0BC7000D6B4F8 /* MapboxCommon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapboxCommon.framework; path = Carthage/Build/iOS/MapboxCommon.framework; sourceTree = ""; }; DA0A4B8024D1C9B200D6B4F8 /* Navigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Navigator.swift; sourceTree = ""; }; + DA0DBA2125FC4CC200E86BDF /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ca; path = Resources/ca.lproj/Localizable.stringsdict; sourceTree = ""; }; DA1811FE20128B0900C91918 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Navigation.strings; sourceTree = ""; }; DA18120120128B7B00C91918 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; DA18120320128E9400C91918 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; @@ -3115,6 +3116,7 @@ 354691B222C0D97000626C4F /* yo */, 8B808F982487D2BE00EEE453 /* el */, DA6C925924C60A43003A0AD6 /* tr */, + DA0DBA2125FC4CC200E86BDF /* ca */, ); name = Localizable.stringsdict; sourceTree = ""; diff --git a/Sources/MapboxNavigation/Resources/ca.lproj/Localizable.stringsdict b/Sources/MapboxNavigation/Resources/ca.lproj/Localizable.stringsdict new file mode 100644 index 00000000000..779bf8dae3a --- /dev/null +++ b/Sources/MapboxNavigation/Resources/ca.lproj/Localizable.stringsdict @@ -0,0 +1,54 @@ + + + + + RATING_ACCESSIBILITY_SET + + NSStringLocalizedFormatKey + %#@stars@ + stars + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Estableix una puntuació de %ld-estels + other + Estableix una puntuació de %ld-estels + + + RATING_STARS_FORMAT + + NSStringLocalizedFormatKey + %#@stars@ + stars + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld estels assignats. + other + %ld estels assignats. + + + NAVIGATION_REPORT_ISSUES + + NSStringLocalizedFormatKey + %#@items@ + items + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Enviar 1 element + other + Enviar %ld elements + + + + From eb09ba08a85378894dd3f2a7e783aee23e57b74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Fri, 12 Mar 2021 17:40:14 -0800 Subject: [PATCH 29/48] Fixed typo in version update script --- scripts/update-version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/update-version.sh b/scripts/update-version.sh index e49de9e7bf4..c79d4578f5b 100755 --- a/scripts/update-version.sh +++ b/scripts/update-version.sh @@ -43,7 +43,7 @@ sed -i '' -E "s/## *main/## ${SHORT_VERSION}/g" CHANGELOG.md # Skip updating the installation instructions for patch releases or prereleases. if [[ $SHORT_VERSION == $SEM_VERSION && $SHORT_VERSION == *.0 ]]; then step "Updating readmes to version ${SEM_VERSION}…" - sed -i '' -E "s/~> *[^']+/~> ${MINOR_VERSION}/g; s/from: \"*[^\"]+/from: \"${SEM_VERSION}/g; s/\`[^\`]+\` as the minimum version/`${SEM_VERSION}` as the minimum version/g" README.md custom-navigation.md + sed -i '' -E "s/~> *[^']+/~> ${MINOR_VERSION}/g; s/from: \"*[^\"]+/from: \"${SEM_VERSION}/g; s/\`[^\`]+\` as the minimum version/\`${SEM_VERSION}\` as the minimum version/g" README.md custom-navigation.md elif [[ $SHORT_VERSION != $SEM_VERSION ]]; then step "Updating readmes to version ${SEM_VERSION}…" sed -i '' -E "s/:tag => 'v[^']+'/:tag => 'v${SEM_VERSION}'/g; s/\"mapbox\/mapbox-navigation-ios\" \"v[^\"]+\"/\"mapbox\/mapbox-navigation-ios\" \"v${SEM_VERSION}\"/g; s/\.exact\(\"*[^\"]+/.exact(\"${SEM_VERSION}/g" README.md custom-navigation.md From 09b331fcb2c509f1f8e608394c087bd85464e866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Fri, 12 Mar 2021 17:41:45 -0800 Subject: [PATCH 30/48] v1.3.0 --- Bench/Info.plist | 2 +- BenchTests/Info.plist | 2 +- Example/Info.plist | 2 +- MapboxCoreNavigation.podspec | 2 +- MapboxNavigation-Documentation.podspec | 2 +- MapboxNavigation.podspec | 2 +- MapboxNavigation.xcodeproj/project.pbxproj | 20 +++++------ README.md | 4 +-- Sources/MapboxCoreNavigation/Info.plist | 2 +- Sources/MapboxNavigation/Info.plist | 2 +- .../PodInstall.xcodeproj/project.pbxproj | 34 ------------------- Tests/CocoaPodsTest/PodInstall/Podfile.lock | 10 +++--- Tests/MapboxCoreNavigationTests/Info.plist | 2 +- Tests/MapboxNavigationTests/Info.plist | 2 +- Tests/TestHelper/Info.plist | 2 +- custom-navigation.md | 10 +++--- 16 files changed, 33 insertions(+), 67 deletions(-) diff --git a/Bench/Info.plist b/Bench/Info.plist index d4337fe862b..70792cc5569 100644 --- a/Bench/Info.plist +++ b/Bench/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.3.0 CFBundleVersion - 43 + 44 LSRequiresIPhoneOS NSLocationAlwaysAndWhenInUseUsageDescription diff --git a/BenchTests/Info.plist b/BenchTests/Info.plist index 2c9ceb8c02a..819f0a2af56 100644 --- a/BenchTests/Info.plist +++ b/BenchTests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.3.0 CFBundleVersion - 43 + 44 diff --git a/Example/Info.plist b/Example/Info.plist index 598ba848538..ec6f00ba4db 100644 --- a/Example/Info.plist +++ b/Example/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 1.3.0 CFBundleVersion - 43 + 44 LSApplicationCategoryType LSRequiresIPhoneOS diff --git a/MapboxCoreNavigation.podspec b/MapboxCoreNavigation.podspec index 0f136eb27e1..326f7695136 100644 --- a/MapboxCoreNavigation.podspec +++ b/MapboxCoreNavigation.podspec @@ -3,7 +3,7 @@ Pod::Spec.new do |s| # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # s.name = "MapboxCoreNavigation" - s.version = '1.3.0-beta.1' + s.version = '1.3.0' s.summary = "Core components for turn-by-turn navigation on iOS." s.description = <<-DESC diff --git a/MapboxNavigation-Documentation.podspec b/MapboxNavigation-Documentation.podspec index b9d989a35a5..ffaaf73ba12 100644 --- a/MapboxNavigation-Documentation.podspec +++ b/MapboxNavigation-Documentation.podspec @@ -3,7 +3,7 @@ Pod::Spec.new do |s| # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # s.name = "MapboxNavigation-Documentation" - s.version = '1.3.0-beta.1' + s.version = '1.3.0' s.summary = "Complete turn-by-turn navigation interface for iOS." s.description = <<-DESC diff --git a/MapboxNavigation.podspec b/MapboxNavigation.podspec index 931e5a2c385..a3537f2f423 100644 --- a/MapboxNavigation.podspec +++ b/MapboxNavigation.podspec @@ -3,7 +3,7 @@ Pod::Spec.new do |s| # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # s.name = "MapboxNavigation" - s.version = '1.3.0-beta.1' + s.version = '1.3.0' s.summary = "Complete turn-by-turn navigation interface for iOS." s.description = <<-DESC diff --git a/MapboxNavigation.xcodeproj/project.pbxproj b/MapboxNavigation.xcodeproj/project.pbxproj index aa5182fe476..b4a5180a369 100644 --- a/MapboxNavigation.xcodeproj/project.pbxproj +++ b/MapboxNavigation.xcodeproj/project.pbxproj @@ -3164,7 +3164,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_TEAM = GJZR2MEM28; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 43; + DYLIB_CURRENT_VERSION = 44; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3190,7 +3190,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_TEAM = GJZR2MEM28; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 43; + DYLIB_CURRENT_VERSION = 44; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3435,12 +3435,12 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = GJZR2MEM28; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 43; + DYLIB_CURRENT_VERSION = 44; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3474,11 +3474,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = GJZR2MEM28; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 43; + DYLIB_CURRENT_VERSION = 44; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3591,7 +3591,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -3656,7 +3656,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3690,7 +3690,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_TEAM = GJZR2MEM28; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 43; + DYLIB_CURRENT_VERSION = 44; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3719,7 +3719,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_TEAM = GJZR2MEM28; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 43; + DYLIB_CURRENT_VERSION = 44; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/README.md b/README.md index 6512457a680..54ef74773c6 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ To install Mapbox Navigation using [CocoaPods](https://cocoapods.org/): 1. Create a [Podfile](https://guides.cocoapods.org/syntax/podfile.html) with the following specification: ```ruby # Latest stable release - pod 'MapboxNavigation', '~> 1.2' + pod 'MapboxNavigation', '~> 1.3' # Latest prerelease pod 'MapboxCoreNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v1.3.0-beta.1' pod 'MapboxNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v1.3.0-beta.1' @@ -76,7 +76,7 @@ To install Mapbox Navigation using [Carthage](https://github.com/Carthage/Cartha 1. Create a [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#github-repositories) with the following dependency: ```cartfile # Latest stable release - github "mapbox/mapbox-navigation-ios" ~> 1.2 + github "mapbox/mapbox-navigation-ios" ~> 1.3 # Latest prerelease github "mapbox/mapbox-navigation-ios" "v1.3.0-beta.1" ``` diff --git a/Sources/MapboxCoreNavigation/Info.plist b/Sources/MapboxCoreNavigation/Info.plist index 17b51b9e8db..b392502bfb2 100644 --- a/Sources/MapboxCoreNavigation/Info.plist +++ b/Sources/MapboxCoreNavigation/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 1.3.0 CFBundleVersion - 43 + 44 NSPrincipalClass diff --git a/Sources/MapboxNavigation/Info.plist b/Sources/MapboxNavigation/Info.plist index 17b51b9e8db..b392502bfb2 100644 --- a/Sources/MapboxNavigation/Info.plist +++ b/Sources/MapboxNavigation/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 1.3.0 CFBundleVersion - 43 + 44 NSPrincipalClass diff --git a/Tests/CocoaPodsTest/PodInstall/PodInstall.xcodeproj/project.pbxproj b/Tests/CocoaPodsTest/PodInstall/PodInstall.xcodeproj/project.pbxproj index 087a3e45ffb..15e834775d5 100644 --- a/Tests/CocoaPodsTest/PodInstall/PodInstall.xcodeproj/project.pbxproj +++ b/Tests/CocoaPodsTest/PodInstall/PodInstall.xcodeproj/project.pbxproj @@ -128,7 +128,6 @@ buildConfigurationList = 352544F91E66623D004C8F1C /* Build configuration list for PBXNativeTarget "PodInstall" */; buildPhases = ( FCAEDB6CF8C116409B047EA1 /* [CP] Check Pods Manifest.lock */, - C0CE5D7A671F38C9FBA189FB /* [CP] Prepare Artifacts */, 352544D81E66623D004C8F1C /* Sources */, 352544D91E66623D004C8F1C /* Frameworks */, 352544DA1E66623D004C8F1C /* Resources */, @@ -232,19 +231,11 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-PodInstall/Pods-PodInstall-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/cocoapods-artifacts-${CONFIGURATION}.txt", "${PODS_ROOT}/Mapbox-iOS-SDK/dynamic/Mapbox.framework", - "${PODS_ROOT}/Mapbox-iOS-SDK/dynamic/Mapbox.framework.dSYM", - "${PODS_ROOT}/Mapbox-iOS-SDK/dynamic/8F3E82C3-52DF-39E5-809D-3389B6CBCAD4.bcsymbolmap", - "${PODS_ROOT}/Mapbox-iOS-SDK/dynamic/0DC8DAD8-C6D7-343B-ACBF-79B48068D7CC.bcsymbolmap", - "${PODS_ROOT}/MapboxAccounts/MapboxAccounts.framework", "${BUILT_PRODUCTS_DIR}/MapboxCoreNavigation/MapboxCoreNavigation.framework", "${BUILT_PRODUCTS_DIR}/MapboxDirections/MapboxDirections.framework", "${BUILT_PRODUCTS_DIR}/MapboxMobileEvents/MapboxMobileEvents.framework", "${BUILT_PRODUCTS_DIR}/MapboxNavigation/MapboxNavigation.framework", - "${PODS_ROOT}/MapboxNavigationNative/MapboxNavigationNative.framework", - "${PODS_ROOT}/MapboxNavigationNative/MapboxNavigationNative.framework.dSYM", - "${PODS_ROOT}/MapboxNavigationNative/4868A5C8-3179-3F0D-ABFC-0C941CC5D2E4.bcsymbolmap", "${BUILT_PRODUCTS_DIR}/MapboxSpeech/MapboxSpeech.framework", "${BUILT_PRODUCTS_DIR}/Polyline/Polyline.framework", "${BUILT_PRODUCTS_DIR}/Solar/Solar.framework", @@ -256,17 +247,10 @@ name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Mapbox.framework", - "${DWARF_DSYM_FOLDER_PATH}/Mapbox.framework.dSYM", - "${BUILT_PRODUCTS_DIR}/8F3E82C3-52DF-39E5-809D-3389B6CBCAD4.bcsymbolmap", - "${BUILT_PRODUCTS_DIR}/0DC8DAD8-C6D7-343B-ACBF-79B48068D7CC.bcsymbolmap", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxAccounts.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCoreNavigation.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxDirections.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMobileEvents.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxNavigation.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxNavigationNative.framework", - "${DWARF_DSYM_FOLDER_PATH}/MapboxNavigationNative.framework.dSYM", - "${BUILT_PRODUCTS_DIR}/4868A5C8-3179-3F0D-ABFC-0C941CC5D2E4.bcsymbolmap", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxSpeech.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Polyline.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Solar.framework", @@ -280,24 +264,6 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PodInstall/Pods-PodInstall-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - C0CE5D7A671F38C9FBA189FB /* [CP] Prepare Artifacts */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-PodInstall/Pods-PodInstall-artifacts.sh", - "${PODS_ROOT}/MapboxCommon/MapboxCommon.xcframework", - ); - name = "[CP] Prepare Artifacts"; - outputPaths = ( - "${BUILT_PRODUCTS_DIR}/cocoapods-artifacts-${CONFIGURATION}.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PodInstall/Pods-PodInstall-artifacts.sh\"\n"; - showEnvVarsInLog = 0; - }; FCAEDB6CF8C116409B047EA1 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/Tests/CocoaPodsTest/PodInstall/Podfile.lock b/Tests/CocoaPodsTest/PodInstall/Podfile.lock index a02f79c11be..9bd56b236b3 100644 --- a/Tests/CocoaPodsTest/PodInstall/Podfile.lock +++ b/Tests/CocoaPodsTest/PodInstall/Podfile.lock @@ -3,7 +3,7 @@ PODS: - MapboxMobileEvents (~> 0.10.4) - MapboxAccounts (2.3.1) - MapboxCommon (9.2.0) - - MapboxCoreNavigation (1.3.0-beta.1): + - MapboxCoreNavigation (1.3.0): - MapboxAccounts (~> 2.3.0) - MapboxDirections (~> 1.2.0) - MapboxMobileEvents (~> 0.10.2) @@ -13,9 +13,9 @@ PODS: - Polyline (~> 5.0) - Turf (~> 1.0) - MapboxMobileEvents (0.10.8) - - MapboxNavigation (1.3.0-beta.1): + - MapboxNavigation (1.3.0): - Mapbox-iOS-SDK (~> 6.0) - - MapboxCoreNavigation (= 1.3.0-beta.1) + - MapboxCoreNavigation (= 1.3.0) - MapboxMobileEvents (~> 0.10.2) - MapboxSpeech (~> 1.0) - Solar (~> 2.1) @@ -53,10 +53,10 @@ SPEC CHECKSUMS: Mapbox-iOS-SDK: 2563ed87ead6ec08f1c2090873977b8a7be335a8 MapboxAccounts: e40ef575df5d8b7ef33d0504ff2d393f4fde0455 MapboxCommon: 2c4ef00c10cba35302835423f4d63c6f3dea4bc4 - MapboxCoreNavigation: c22313c631a2b1bc51da3f3b57d2ce4c93ba38ee + MapboxCoreNavigation: 77b3db040855684b0f2cd9064e5d9bd51c9f5739 MapboxDirections: 383df0cd65784897c2269750a42d9654c832f0ce MapboxMobileEvents: 36ff53b135aac486eed94b61f813c7967a0c2c6f - MapboxNavigation: 64c51ef4832ac09d80b67022daedfe06af39eb6e + MapboxNavigation: 222274f1330cf9336529ade519a14df4042dfd9f MapboxNavigationNative: 0bc54a7cf20a892a96eca07e1f05776f496491f5 MapboxSpeech: 4b3aea42e35d056fae1d7ad847a9fc0f412d911e Polyline: fce41d72e1146c41c6d081f7656827226f643dff diff --git a/Tests/MapboxCoreNavigationTests/Info.plist b/Tests/MapboxCoreNavigationTests/Info.plist index 0cfb04e087f..c17d7352871 100644 --- a/Tests/MapboxCoreNavigationTests/Info.plist +++ b/Tests/MapboxCoreNavigationTests/Info.plist @@ -21,6 +21,6 @@ NSLocationAlwaysAndWhenInUseUsageDescription Location Usage Description CFBundleVersion - 43 + 44 diff --git a/Tests/MapboxNavigationTests/Info.plist b/Tests/MapboxNavigationTests/Info.plist index 3bfeb4c59b9..f7ff3086af6 100644 --- a/Tests/MapboxNavigationTests/Info.plist +++ b/Tests/MapboxNavigationTests/Info.plist @@ -21,6 +21,6 @@ NSLocationWhenInUseUsageDescription Location Usage Description CFBundleVersion - 43 + 44 diff --git a/Tests/TestHelper/Info.plist b/Tests/TestHelper/Info.plist index 11b7e76173e..dd8f44a6288 100644 --- a/Tests/TestHelper/Info.plist +++ b/Tests/TestHelper/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.3.0 CFBundleVersion - 43 + 44 diff --git a/custom-navigation.md b/custom-navigation.md index f87f5b6043f..09cf3d299c9 100644 --- a/custom-navigation.md +++ b/custom-navigation.md @@ -30,7 +30,7 @@ To install Mapbox Core Navigation using [CocoaPods](https://cocoapods.org/): 1. Create a [Podfile](https://guides.cocoapods.org/syntax/podfile.html) with the following specification: ```ruby # Latest stable release - pod 'MapboxCoreNavigation', '~> 1.2' + pod 'MapboxCoreNavigation', '~> 1.3' # Latest prerelease pod 'MapboxCoreNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v1.3.0-beta.1' ``` @@ -57,7 +57,7 @@ To install Mapbox Navigation using [Carthage](https://github.com/Carthage/Cartha 1. Create a [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#github-repositories) with the following dependency: ```cartfile # Latest stable release - github "mapbox/mapbox-navigation-ios" ~> 1.2 + github "mapbox/mapbox-navigation-ios" ~> 1.3 # Latest prerelease github "mapbox/mapbox-navigation-ios" "v1.3.0-beta.1" ``` @@ -81,9 +81,9 @@ To install the MapboxCoreNavigation framework using [Swift Package Manager](http 1. Run `swift package init` to create a Package.swift, then add the following dependency: ```swift // Latest stable release - .package(name: "MapboxCoreNavigation", url: "https://github.com/mapbox/mapbox-navigation-ios.git", from: "1.2.0") + .package(name: "MapboxCoreNavigation", url: "https://github.com/mapbox/mapbox-navigation-ios.git", from: "1.3.0") // Latest prerelease - .package(name: "MapboxCoreNavigation", url: "https://github.com/mapbox/mapbox-navigation-ios.git", from: "1.2.0") + .package(name: "MapboxCoreNavigation", url: "https://github.com/mapbox/mapbox-navigation-ios.git", from: "1.3.0") ``` ### Using Xcode @@ -102,4 +102,4 @@ To install the MapboxCoreNavigation framework using [Swift Package Manager](http 1. Enter `https://github.com/mapbox/mapbox-navigation-ios.git` as the package repository and click Next. -1. Set Rules to Version, Up to Next Major, and enter `1.2.0` as the minimum version requirement. Click Next. +1. Set Rules to Version, Up to Next Major, and enter `1.3.0` as the minimum version requirement. Click Next. From a882ae351cad8cf2492978eadecef5691c4464d5 Mon Sep 17 00:00:00 2001 From: Alex Azarov Date: Wed, 17 Mar 2021 12:15:13 +0300 Subject: [PATCH 31/48] Don't trigger viewDidLoad in NavigationViewController.init --- .../NavigationViewController.swift | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Sources/MapboxNavigation/NavigationViewController.swift b/Sources/MapboxNavigation/NavigationViewController.swift index a02752aac26..42f5e1908ab 100644 --- a/Sources/MapboxNavigation/NavigationViewController.swift +++ b/Sources/MapboxNavigation/NavigationViewController.swift @@ -329,18 +329,11 @@ open class NavigationViewController: UIViewController, NavigationStatusPresenter mapViewController.destination = route.legs.last?.destination mapViewController.view.translatesAutoresizingMaskIntoConstraints = false - embed(mapViewController, in: view) { (parent, map) -> [NSLayoutConstraint] in - return map.view.constraintsForPinning(to: parent.view) - } - //Manually update the map style since the RMVC missed the "map style change" notification when the style manager was set up. if let currentStyle = styleManager.currentStyle { updateMapStyle(currentStyle, animated: false) } - mapViewController.view.pinInSuperview() - mapViewController.reportButton.isHidden = !showsReportFeedback - if !(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.") } @@ -365,6 +358,15 @@ open class NavigationViewController: UIViewController, NavigationStatusPresenter override open func viewDidLoad() { super.viewDidLoad() + + if let mapViewController = mapViewController { + embed(mapViewController, in: view) { (parent, map) -> [NSLayoutConstraint] in + return map.view.constraintsForPinning(to: parent.view) + } + mapViewController.view.pinInSuperview() + mapViewController.reportButton.isHidden = !showsReportFeedback + } + // Initialize voice controller if it hasn't been overridden. // This is optional and lazy so it can be mutated by the developer after init. _ = voiceController From 004b13fe192b137b63ec1717ce755237f4365c47 Mon Sep 17 00:00:00 2001 From: Alex Azarov Date: Thu, 18 Mar 2021 20:00:52 +0300 Subject: [PATCH 32/48] Add PR #2871 to the CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e594a85008..bbde0dd0887 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to the Mapbox Navigation SDK for iOS +## v1.4.0 + +* Fixed an issue when feedback UI was always appearing for short routes ([#2871](https://github.com/mapbox/mapbox-navigation-ios/pull/2871)) + ## v1.3.0 * MapboxCoreNavigation can now be installed using Swift Package Manager. ([#2771](https://github.com/mapbox/mapbox-navigation-ios/pull/2771)) From a56e60daf8eb3a23cb52ae3e338c82fd2e38c26a Mon Sep 17 00:00:00 2001 From: Alex Azarov Date: Tue, 23 Mar 2021 15:13:51 +0300 Subject: [PATCH 33/48] Fix automatic day/night switching --- Sources/MapboxNavigation/StyleManager.swift | 9 ++++----- Sources/MapboxNavigation/VanishingRouteLine.swift | 7 ++++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Sources/MapboxNavigation/StyleManager.swift b/Sources/MapboxNavigation/StyleManager.swift index aa52e1f61ab..197a447dd00 100644 --- a/Sources/MapboxNavigation/StyleManager.swift +++ b/Sources/MapboxNavigation/StyleManager.swift @@ -131,12 +131,10 @@ open class StyleManager { print("Unable to get sunrise or sunset. Automatic style switching has been disabled.") return } - - timeOfDayTimer = Timer(timeInterval: interval + 1, - repeats: false, - block: { [weak self] _ in + + timeOfDayTimer = Timer.scheduledTimer(withTimeInterval: interval + 1, repeats: false) { [weak self] _ in self?.timeOfDayChanged() - }) + } } @objc func preferredContentSizeChanged(_ notification: Notification) { @@ -159,6 +157,7 @@ open class StyleManager { currentStyleType = styleType currentStyle = style delegate?.styleManager(self, didApply: style) + break } } diff --git a/Sources/MapboxNavigation/VanishingRouteLine.swift b/Sources/MapboxNavigation/VanishingRouteLine.swift index 742c5094c4f..0ca985fb9d6 100644 --- a/Sources/MapboxNavigation/VanishingRouteLine.swift +++ b/Sources/MapboxNavigation/VanishingRouteLine.swift @@ -205,7 +205,12 @@ extension NavigationMapView { let newFractionTraveled = self.preFractionTraveled + traveledDifference * timePassedInMilliseconds.truncatingRemainder(dividingBy: 1000) / 1000 guard let mainRouteLayerGradient = self.routeLineGradient(routeProgress.route, fractionTraveled: newFractionTraveled) else { return } let mainCasingLayerStops = self.routeCasingGradient(newFractionTraveled) - + + guard let mainRouteLayer = self.style?.layer(withIdentifier: mainRouteLayerIdentifier) as? MGLLineStyleLayer, + let mainRouteCasingLayer = self.style?.layer(withIdentifier: mainRouteCasingLayerIdentifier) as? MGLLineStyleLayer else { + timer.invalidate() + return + } mainRouteLayer.lineGradient = mainRouteLayerGradient mainRouteCasingLayer.lineGradient = mainCasingLayerStops }) From 069564c4f4958bad2e1b6eff7c80ed216d4dfedd Mon Sep 17 00:00:00 2001 From: Alex Azarov Date: Tue, 23 Mar 2021 15:52:03 +0300 Subject: [PATCH 34/48] update changelog with pr #2881 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbde0dd0887..7e5c541df8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## v1.4.0 * Fixed an issue when feedback UI was always appearing for short routes ([#2871](https://github.com/mapbox/mapbox-navigation-ios/pull/2871)) +* Fixed automatic day/night switching ([#2881](https://github.com/mapbox/mapbox-navigation-ios/pull/2881)) ## v1.3.0 From 45d0ce911fc31473f1fd197c769ff249f4a25c6e Mon Sep 17 00:00:00 2001 From: Demetri Miller Date: Mon, 29 Mar 2021 10:42:31 -0500 Subject: [PATCH 35/48] Fix crash when switching between day / night modes --- Sources/MapboxNavigation/NavigationMapView.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/MapboxNavigation/NavigationMapView.swift b/Sources/MapboxNavigation/NavigationMapView.swift index c371dcde999..6d93e627f6e 100755 --- a/Sources/MapboxNavigation/NavigationMapView.swift +++ b/Sources/MapboxNavigation/NavigationMapView.swift @@ -281,6 +281,12 @@ open class NavigationMapView: MGLMapView, UIGestureRecognizerDelegate { } + open override func reloadStyle(_ sender: Any?) { + vanishingRouteLineUpdateTimer?.invalidate() + vanishingRouteLineUpdateTimer = nil + super.reloadStyle(sender) + } + open override func layoutMarginsDidChange() { super.layoutMarginsDidChange() enableFrameByFrameCourseViewTracking(for: 3) From b81849f4947ccb3a7a6da05dc7e1460d6c924163 Mon Sep 17 00:00:00 2001 From: Demetri Miller Date: Mon, 29 Mar 2021 11:00:16 -0500 Subject: [PATCH 36/48] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e5c541df8b..e2009732349 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Fixed an issue when feedback UI was always appearing for short routes ([#2871](https://github.com/mapbox/mapbox-navigation-ios/pull/2871)) * Fixed automatic day/night switching ([#2881](https://github.com/mapbox/mapbox-navigation-ios/pull/2881)) +* Fixed crash when switching between day / night modes ([#2896](https://github.com/mapbox/mapbox-navigation-ios/pull/2896)) ## v1.3.0 From 9632c9649929fa91c5383f7308488bf071347eec Mon Sep 17 00:00:00 2001 From: Demetri Miller Date: Tue, 30 Mar 2021 11:32:18 -0500 Subject: [PATCH 37/48] Update scoping of StyleManager props to public (#2888) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update scoping of StyleManager props to public Changes the scope of `NavigationViewController.styleManager`, `StyleManager.currentStyleType`, and `StyleManager.currentStyle` to `public private(set)` allowing consumers to programmatically apply a StyleType. Addresses #2101 cc @mapbox/navigation-ios * Add PR #2888 to the CHANGELOG.md * Update CHANGELOG.md Co-authored-by: Minh Nguyễn * Add documentation comments to public-facing props * Update Sources/MapboxNavigation/StyleManager.swift Co-authored-by: Minh Nguyễn Co-authored-by: Demetri Miller Co-authored-by: Minh Nguyễn --- CHANGELOG.md | 1 + .../NavigationViewController.swift | 7 ++++++- Sources/MapboxNavigation/StyleManager.swift | 17 ++++++++++++++--- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2009732349..9cfa1e62c36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Fixed an issue when feedback UI was always appearing for short routes ([#2871](https://github.com/mapbox/mapbox-navigation-ios/pull/2871)) * Fixed automatic day/night switching ([#2881](https://github.com/mapbox/mapbox-navigation-ios/pull/2881)) +* Added the `NavigationViewController.styleManager`, `StyleManager.currentStyleType`, and `StyleManager.currentStyle` properties and the `StyleManager.applyStyle(type:)` method to manually change the UI style at any time. ([#2888](https://github.com/mapbox/mapbox-navigation-ios/pull/2888)) * Fixed crash when switching between day / night modes ([#2896](https://github.com/mapbox/mapbox-navigation-ios/pull/2896)) ## v1.3.0 diff --git a/Sources/MapboxNavigation/NavigationViewController.swift b/Sources/MapboxNavigation/NavigationViewController.swift index 42f5e1908ab..eb4de6e0d74 100644 --- a/Sources/MapboxNavigation/NavigationViewController.swift +++ b/Sources/MapboxNavigation/NavigationViewController.swift @@ -261,7 +261,12 @@ open class NavigationViewController: UIViewController, NavigationStatusPresenter */ public var annotatesSpokenInstructions = false - var styleManager: StyleManager! + /** + Controls the styling of NavigationViewController and its components. + + The style can be modified programmatically by using `StyleManager.applyStyle(type:)`. + */ + public private(set) var styleManager: StyleManager! var currentStatusBarStyle: UIStatusBarStyle = .default diff --git a/Sources/MapboxNavigation/StyleManager.swift b/Sources/MapboxNavigation/StyleManager.swift index 197a447dd00..28547ddae72 100644 --- a/Sources/MapboxNavigation/StyleManager.swift +++ b/Sources/MapboxNavigation/StyleManager.swift @@ -87,8 +87,16 @@ open class StyleManager { internal var date: Date? private var timeOfDayTimer: Timer? - var currentStyleType: StyleType? - private(set) var currentStyle: Style? { + /** + The currently applied style. Use `StyleManager.applyStyle(type:)` to update this value. + */ + public private(set) var currentStyleType: StyleType? + + /** + The current style associated with `currentStyleType`. Calling `StyleManager.applyStyle(type:)` will + result in this value being updated. + */ + public private(set) var currentStyle: Style? { didSet { guard let style = currentStyle else { return } postDidApplyStyleNotification(style: style) @@ -146,7 +154,10 @@ open class StyleManager { resetTimeOfDayTimer() } - func applyStyle(type styleType: StyleType) { + /** + Applies the `Style` with type matching `type`and notifies `StyleManager.delegate` upon completion. + */ + public func applyStyle(type styleType: StyleType) { guard currentStyleType != styleType else { return } NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(timeOfDayChanged), object: nil) From b8e6829184d9d49f7279073bebb605c802fcd6b4 Mon Sep 17 00:00:00 2001 From: Demetri Miller Date: Thu, 1 Apr 2021 10:27:06 -0500 Subject: [PATCH 38/48] Refactor StyleManager's appearance refresh logic to no longer use UIWindow loop (#2897) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor StyleManager appearance refresh logic to no longer use UIWindows * Updated changelog * Typos * Update delegate name per PR feedback` https://github.com/mapbox/mapbox-navigation-ios/pull/2897#pullrequestreview-625799444 * Update CHANGELOG.md Co-authored-by: Minh Nguyễn Co-authored-by: Demetri Miller Co-authored-by: Minh Nguyễn --- CHANGELOG.md | 2 ++ Sources/MapboxNavigation/StyleManager.swift | 32 +++++++++++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cfa1e62c36..04e73242040 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ * Fixed an issue when feedback UI was always appearing for short routes ([#2871](https://github.com/mapbox/mapbox-navigation-ios/pull/2871)) * Fixed automatic day/night switching ([#2881](https://github.com/mapbox/mapbox-navigation-ios/pull/2881)) +* Fixed an issue where presenting `NavigationViewController` could sometimes interfere with view presentation in other windows. ([#2897](https://github.com/mapbox/mapbox-navigation-ios/pull/2897)) +* Added an optional `StyleManagerDelegate.styleManager(_:viewForApplying:)` method to determine which part of the view hierarchy is affected by a change to a different `Style`. ([#2897](https://github.com/mapbox/mapbox-navigation-ios/pull/2897)) * Added the `NavigationViewController.styleManager`, `StyleManager.currentStyleType`, and `StyleManager.currentStyle` properties and the `StyleManager.applyStyle(type:)` method to manually change the UI style at any time. ([#2888](https://github.com/mapbox/mapbox-navigation-ios/pull/2888)) * Fixed crash when switching between day / night modes ([#2896](https://github.com/mapbox/mapbox-navigation-ios/pull/2896)) diff --git a/Sources/MapboxNavigation/StyleManager.swift b/Sources/MapboxNavigation/StyleManager.swift index 28547ddae72..c9a611ef304 100644 --- a/Sources/MapboxNavigation/StyleManager.swift +++ b/Sources/MapboxNavigation/StyleManager.swift @@ -11,6 +11,14 @@ public protocol StyleManagerDelegate: class, UnimplementedLogging { */ func location(for styleManager: StyleManager) -> CLLocation? + /** + Asks the delegate for the view to be used when refreshing appearance. + + The default implementation of this method will attempt to cast the delegate to type + `UIViewController` and use its `view` property. + */ + func styleManager(_ styleManager: StyleManager, viewForApplying currentStyle: Style?) -> UIView? + /** Informs the delegate that a style was applied. @@ -46,6 +54,16 @@ public extension StyleManagerDelegate { func styleManagerDidRefreshAppearance(_ styleManager: StyleManager) { logUnimplemented(protocolType: StyleManagerDelegate.self, level: .debug) } + + func styleManager(_ styleManager: StyleManager, viewForApplying currentStyle: Style?) -> UIView? { + // Short-circuit refresh logic if the view hasn't yet loaded since we don't want the `self.view` + // call to trigger `loadView`. + if let vc = self as? UIViewController, vc.isViewLoaded { + return vc.view + } + + return nil + } } /** @@ -236,13 +254,15 @@ open class StyleManager { forceRefreshAppearance() } - // workaround to refresh appearance by removing all views and then adding them again + // workaround to refresh appearance by removing the view and then adding it again func forceRefreshAppearance() { - for window in UIApplication.shared.windows { - for view in window.subviews { - view.removeFromSuperview() - window.addSubview(view) - } + if + let view = delegate?.styleManager(self, viewForApplying: currentStyle), + let superview = view.superview, + let index = superview.subviews.firstIndex(of: view) + { + view.removeFromSuperview() + superview.insertSubview(view, at: index) } delegate?.styleManagerDidRefreshAppearance(self) From 641e9406ad626e652cb264e4d200db31a9913d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Fri, 2 Apr 2021 11:55:50 -0700 Subject: [PATCH 39/48] MapboxNavigationNative v32.0.0 --- CHANGELOG.md | 1 + Cartfile | 2 +- Cartfile.resolved | 6 +++--- MapboxCoreNavigation.podspec | 2 +- MapboxNavigation-Documentation.podspec | 2 +- Tests/CocoaPodsTest/PodInstall/Podfile.lock | 8 ++++---- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04e73242040..394c6ecb8e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## v1.4.0 +* Increased the minimum version of `MapboxNavigationNative` to v32.0.0. ([#2910](https://github.com/mapbox/mapbox-navigation-ios/pull/2910)) * Fixed an issue when feedback UI was always appearing for short routes ([#2871](https://github.com/mapbox/mapbox-navigation-ios/pull/2871)) * Fixed automatic day/night switching ([#2881](https://github.com/mapbox/mapbox-navigation-ios/pull/2881)) * Fixed an issue where presenting `NavigationViewController` could sometimes interfere with view presentation in other windows. ([#2897](https://github.com/mapbox/mapbox-navigation-ios/pull/2897)) diff --git a/Cartfile b/Cartfile index 147f3628130..6e8c35677a9 100644 --- a/Cartfile +++ b/Cartfile @@ -1,5 +1,5 @@ binary "https://www.mapbox.com/ios-sdk/MapboxAccounts.json" ~> 2.3.0 -binary "https://api.mapbox.com/downloads/v2/carthage/mobile-navigation-native/MapboxNavigationNative.json" ~> 31.0 +binary "https://api.mapbox.com/downloads/v2/carthage/mobile-navigation-native/MapboxNavigationNative.json" ~> 32.0 binary "https://api.mapbox.com/downloads/v2/carthage/mobile-maps/mapbox-ios-sdk-dynamic.json" ~> 6.0 binary "https://api.mapbox.com/downloads/v2/carthage/mapbox-common/MapboxCommon-ios.json" == 9.2.0 github "mapbox/mapbox-directions-swift" ~> 1.2.0 diff --git a/Cartfile.resolved b/Cartfile.resolved index 449307a5549..769a35c63f1 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,17 +1,17 @@ binary "https://api.mapbox.com/downloads/v2/carthage/mapbox-common/MapboxCommon-ios.json" "9.2.0" binary "https://api.mapbox.com/downloads/v2/carthage/mobile-maps/mapbox-ios-sdk-dynamic.json" "6.3.0" -binary "https://api.mapbox.com/downloads/v2/carthage/mobile-navigation-native/MapboxNavigationNative.json" "31.0.1" +binary "https://api.mapbox.com/downloads/v2/carthage/mobile-navigation-native/MapboxNavigationNative.json" "32.0.0" binary "https://www.mapbox.com/ios-sdk/MapboxAccounts.json" "2.3.1" github "CedarBDD/Cedar" "v1.0" github "Quick/Nimble" "v8.1.2" github "Quick/Quick" "v2.2.1" github "Udumft/SnappyShrimp" "66f3e3ba70370ad5e889ac8ed72a8730ca79958e" github "Udumft/SwiftCLI" "da19d2a16cd5aa838d8fb7256e28c171bc67dd82" -github "ceeK/Solar" "2.1.0" +github "ceeK/Solar" "2.2.0" github "linksmt/OHHTTPStubs" "563f48d3fab84ef04639649c770b00f4fa502cca" github "mapbox/MapboxGeocoder.swift" "v0.10.2" github "mapbox/mapbox-directions-swift" "v1.2.0" -github "mapbox/mapbox-events-ios" "v0.10.7" +github "mapbox/mapbox-events-ios" "v0.10.8" github "mapbox/mapbox-speech-swift" "v1.0.0" github "mapbox/turf-swift" "v1.2.0" github "raphaelmor/Polyline" "v5.0.2" diff --git a/MapboxCoreNavigation.podspec b/MapboxCoreNavigation.podspec index 326f7695136..4785c44688d 100644 --- a/MapboxCoreNavigation.podspec +++ b/MapboxCoreNavigation.podspec @@ -40,7 +40,7 @@ Pod::Spec.new do |s| s.requires_arc = true s.module_name = "MapboxCoreNavigation" - s.dependency "MapboxNavigationNative", "~> 31.0" + s.dependency "MapboxNavigationNative", "~> 32.0" s.dependency "MapboxAccounts", "~> 2.3.0" s.dependency "MapboxDirections", "~> 1.2.0" s.dependency "MapboxMobileEvents", "~> 0.10.2" # Always specify a patch release if pre-v1.0 diff --git a/MapboxNavigation-Documentation.podspec b/MapboxNavigation-Documentation.podspec index ffaaf73ba12..ebe43efd307 100644 --- a/MapboxNavigation-Documentation.podspec +++ b/MapboxNavigation-Documentation.podspec @@ -50,7 +50,7 @@ Pod::Spec.new do |s| s.dependency "MapboxGeocoder.swift", "~> 0.10.0" s.dependency "Mapbox-iOS-SDK", "~> 6.0" s.dependency "MapboxMobileEvents", "~> 0.10.2" - s.dependency "MapboxNavigationNative", "~> 31.0" + s.dependency "MapboxNavigationNative", "~> 32.0" s.dependency "Solar", "~> 2.1" s.dependency "Turf", "~> 1.0" s.dependency "MapboxSpeech", "~> 1.0" diff --git a/Tests/CocoaPodsTest/PodInstall/Podfile.lock b/Tests/CocoaPodsTest/PodInstall/Podfile.lock index 9bd56b236b3..76649c13e3c 100644 --- a/Tests/CocoaPodsTest/PodInstall/Podfile.lock +++ b/Tests/CocoaPodsTest/PodInstall/Podfile.lock @@ -7,7 +7,7 @@ PODS: - MapboxAccounts (~> 2.3.0) - MapboxDirections (~> 1.2.0) - MapboxMobileEvents (~> 0.10.2) - - MapboxNavigationNative (~> 31.0) + - MapboxNavigationNative (~> 32.0) - Turf (~> 1.0) - MapboxDirections (1.2.0): - Polyline (~> 5.0) @@ -19,7 +19,7 @@ PODS: - MapboxMobileEvents (~> 0.10.2) - MapboxSpeech (~> 1.0) - Solar (~> 2.1) - - MapboxNavigationNative (31.0.1): + - MapboxNavigationNative (32.0.0): - MapboxCommon (= 9.2.0) - MapboxSpeech (1.0.0) - Polyline (5.0.2) @@ -53,11 +53,11 @@ SPEC CHECKSUMS: Mapbox-iOS-SDK: 2563ed87ead6ec08f1c2090873977b8a7be335a8 MapboxAccounts: e40ef575df5d8b7ef33d0504ff2d393f4fde0455 MapboxCommon: 2c4ef00c10cba35302835423f4d63c6f3dea4bc4 - MapboxCoreNavigation: 77b3db040855684b0f2cd9064e5d9bd51c9f5739 + MapboxCoreNavigation: 5733798e2747310ffd410105420ef5dc64d02620 MapboxDirections: 383df0cd65784897c2269750a42d9654c832f0ce MapboxMobileEvents: 36ff53b135aac486eed94b61f813c7967a0c2c6f MapboxNavigation: 222274f1330cf9336529ade519a14df4042dfd9f - MapboxNavigationNative: 0bc54a7cf20a892a96eca07e1f05776f496491f5 + MapboxNavigationNative: e811b9beed18c01e4693e29f843d36b4a760fa61 MapboxSpeech: 4b3aea42e35d056fae1d7ad847a9fc0f412d911e Polyline: fce41d72e1146c41c6d081f7656827226f643dff Solar: 2dc6e7cc39186cb0c8228fa08df76fb50c7d8f24 From 85170c55d4563e9d13cb168207d6796198060ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Fri, 2 Apr 2021 14:31:52 -0700 Subject: [PATCH 40/48] Avoid building Carthage dependencies for SPM --- .circleci/config.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e5bf3bde0ef..47a35087e35 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -84,7 +84,9 @@ step-library: - &install-dependencies unless: condition: - equal: [ "12.1.0", << parameters.xcode >> ] + or: + - equal: [ "12.1.0", << parameters.xcode >> ] + - equal: [ true, << parameters.spm >> ] steps: run: name: Install Dependencies @@ -95,7 +97,9 @@ step-library: - &install-dependencies-12 when: condition: - equal: [ "12.1.0", << parameters.xcode >> ] + and: + - equal: [ "12.1.0", << parameters.xcode >> ] + - not: << parameters.spm >> steps: run: name: Install Dependencies @@ -246,6 +250,9 @@ jobs: xcode: type: string default: "11.4.1" + spm: + type: boolean + default: false macos: xcode: << parameters.xcode >> environment: From efac4a4b6975bddf7be36765c6692882b8c0227e Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Tue, 6 Apr 2021 11:03:33 -0700 Subject: [PATCH 41/48] Implement support for CPTemplateApplicationSceneDelegate UIScene-based CarPlay on iOS 13 and above (#2832) * Add support for UIScene-based CarPlay API. --- CHANGELOG.md | 1 + Example/AppDelegate+CarPlay.swift | 26 +++++++- Example/AppDelegate.swift | 29 +++++++++ Example/FavoritesList.swift | 4 -- Example/Info.plist | 32 +++++++++ MapboxNavigation.xcodeproj/project.pbxproj | 10 +-- Sources/MapboxNavigation/CPMapTemplate.swift | 2 - Sources/MapboxNavigation/CPTrip.swift | 2 - Sources/MapboxNavigation/CarPlayManager.swift | 65 ++++++++++++++----- .../CarPlayManagerDelegate.swift | 11 +++- .../CarPlayMapViewController.swift | 2 - .../CarPlayNavigationViewController.swift | 2 - ...hController+CPSearchTemplateDelegate.swift | 2 +- .../CarPlaySearchController.swift | 7 -- .../MapboxNavigation/CongestionLevel.swift | 2 - Sources/MapboxNavigation/DayStyle.swift | 8 ++- Sources/MapboxNavigation/NightStyle.swift | 7 +- Sources/MapboxNavigation/RecentItem.swift | 2 +- Sources/MapboxNavigation/UIScreen.swift | 2 - .../MapboxNavigation/VisualInstruction.swift | 4 -- .../CPMapTemplateTests.swift | 2 - .../CarPlayManagerTests.swift | 14 +++- 22 files changed, 173 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 394c6ecb8e9..d241ae426e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Added an optional `StyleManagerDelegate.styleManager(_:viewForApplying:)` method to determine which part of the view hierarchy is affected by a change to a different `Style`. ([#2897](https://github.com/mapbox/mapbox-navigation-ios/pull/2897)) * Added the `NavigationViewController.styleManager`, `StyleManager.currentStyleType`, and `StyleManager.currentStyle` properties and the `StyleManager.applyStyle(type:)` method to manually change the UI style at any time. ([#2888](https://github.com/mapbox/mapbox-navigation-ios/pull/2888)) * Fixed crash when switching between day / night modes ([#2896](https://github.com/mapbox/mapbox-navigation-ios/pull/2896)) +* Add support for iOS 13's UIScene based CarPlay API ([#2832](https://github.com/mapbox/mapbox-navigation-ios/pull/2832)) ## v1.3.0 diff --git a/Example/AppDelegate+CarPlay.swift b/Example/AppDelegate+CarPlay.swift index facf72be3f9..e01ea48d1ac 100644 --- a/Example/AppDelegate+CarPlay.swift +++ b/Example/AppDelegate+CarPlay.swift @@ -1,6 +1,5 @@ import UIKit import MapboxNavigation -#if canImport(CarPlay) import CarPlay import MapboxCoreNavigation import MapboxDirections @@ -147,6 +146,10 @@ extension AppDelegate: CarPlayManagerDelegate { return nil } } + + func carPlayManager(_ carPlayManager: CarPlayManager, didPresent navigationViewController: CarPlayNavigationViewController) { + // no-op + } } @available(iOS 12.0, *) @@ -184,4 +187,23 @@ extension AppDelegate: CPListTemplateDelegate { completionHandler() } } -#endif + +@available(iOS 13.0, *) +class CarPlaySceneDelegate: NSObject, CPTemplateApplicationSceneDelegate { + + func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, + didConnect interfaceController: CPInterfaceController, to window: CPWindow) { + + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } + appDelegate.carPlayManager.delegate = appDelegate + appDelegate.carPlaySearchController.delegate = appDelegate + appDelegate.carPlayManager.templateApplicationScene(templateApplicationScene, didConnectCarInterfaceController: interfaceController, to: window) + } + + func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, + didDisconnect interfaceController: CPInterfaceController, from window: CPWindow) { + + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } + appDelegate.carPlayManager.templateApplicationScene(templateApplicationScene, didDisconnectCarInterfaceController: interfaceController, from: window) + } +} diff --git a/Example/AppDelegate.swift b/Example/AppDelegate.swift index 4501ee6d787..62401328435 100644 --- a/Example/AppDelegate.swift +++ b/Example/AppDelegate.swift @@ -1,5 +1,6 @@ import UIKit import MapboxNavigation +import CarPlay @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -12,8 +13,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate { @available(iOS 12.0, *) lazy var carPlaySearchController: CarPlaySearchController = CarPlaySearchController() + @available(iOS 12.0, *) + lazy var interfaceController: CPInterfaceController? = nil + + @available(iOS 12.0, *) + lazy var carWindow: CPWindow? = nil + + @available(iOS 12.0, *) + lazy var sessionConfiguration: CPSessionConfiguration? = nil + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { if isRunningTests() { + if window == nil { + window = UIWindow(frame: UIScreen.main.bounds) + } window!.rootViewController = UIViewController() } @@ -37,3 +50,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } } + +@available(iOS 13.0, *) +extension AppDelegate: UIWindowSceneDelegate { + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + + if connectingSceneSession.role == .carTemplateApplication { + return UISceneConfiguration(name: "ExampleCarPlayApplicationConfiguration", sessionRole: connectingSceneSession.role) + } + return UISceneConfiguration(name: "ExampleAppConfiguration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + + } +} diff --git a/Example/FavoritesList.swift b/Example/FavoritesList.swift index 3e811319f2e..a9c171f18f3 100644 --- a/Example/FavoritesList.swift +++ b/Example/FavoritesList.swift @@ -1,6 +1,4 @@ -#if canImport(CarPlay) import CarPlay -#endif import CoreLocation public enum FavoritesList { @@ -48,11 +46,9 @@ public enum FavoritesList { } } - #if canImport(CarPlay) @available(iOS 12.0, *) func listItem() -> CPListItem { return CPListItem(text: rawValue, detailText: subTitle, image: nil, showsDisclosureIndicator: true) } - #endif } } diff --git a/Example/Info.plist b/Example/Info.plist index ec6f00ba4db..b2ad20b39c7 100644 --- a/Example/Info.plist +++ b/Example/Info.plist @@ -32,6 +32,38 @@ Get user location NSLocationWhenInUseUsageDescription Get user location + UIApplicationSceneManifest + + CPSupportsDashboardNavigationScene + + UISceneConfigurations + + CPTemplateApplicationSceneSessionRoleApplication + + + UISceneClassName + CPTemplateApplicationScene + UISceneConfigurationName + ExampleCarPlayApplicationConfiguration + UISceneDelegateClassName + Example.CarPlaySceneDelegate + + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + ExampleAppConfiguration + UISceneDelegateClassName + $(EXECUTABLE_NAME).AppDelegate + UISceneStoryboardFile + Main + + + + UIBackgroundModes audio diff --git a/MapboxNavigation.xcodeproj/project.pbxproj b/MapboxNavigation.xcodeproj/project.pbxproj index b4a5180a369..a4930e50399 100644 --- a/MapboxNavigation.xcodeproj/project.pbxproj +++ b/MapboxNavigation.xcodeproj/project.pbxproj @@ -905,7 +905,7 @@ C53C19751F38EADE008DB406 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; C53C19771F38EAE4008DB406 /* ca */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = ""; }; C53C197A1F38EAEA008DB406 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; - C53F2F0720EBC95600D9798F /* Example-CarPlay.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example-CarPlay.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + C53F2F0720EBC95600D9798F /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; C549F8311F17F2C5001A0A2D /* MapboxMobileEvents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapboxMobileEvents.framework; path = Carthage/Build/iOS/MapboxMobileEvents.framework; sourceTree = ""; }; C54C655120336F2600D338E0 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; C551B0E520D42222009A986F /* NavigationLocationManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationLocationManagerTests.swift; sourceTree = ""; }; @@ -1779,7 +1779,7 @@ 351BEBD71E5BCC28006FE110 /* MapboxNavigation.framework */, 358D14631E5E3B7700ADE590 /* Example.app */, 35B711CF1E5E7AD2001EDA8D /* MapboxNavigationTests.xctest */, - C53F2F0720EBC95600D9798F /* Example-CarPlay.app */, + C53F2F0720EBC95600D9798F /* Example.app */, 35CDA80621908F2F0072B675 /* Bench.app */, 35CDA81921908F320072B675 /* BenchTests.xctest */, 35CDA85E2190F2A30072B675 /* TestHelper.framework */, @@ -2045,7 +2045,7 @@ ); name = "Example-CarPlay"; productName = "Example-Swift"; - productReference = C53F2F0720EBC95600D9798F /* Example-CarPlay.app */; + productReference = C53F2F0720EBC95600D9798F /* Example.app */; productType = "com.apple.product-type.application"; }; C5ADFBC81DDCC7840011824B /* MapboxCoreNavigation */ = { @@ -3521,7 +3521,7 @@ "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.mapbox.Example-CarPlay"; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = Example; PROVISIONING_PROFILE = "69c90fd8-c53b-41a4-ac73-5bc11068a49a"; PROVISIONING_PROFILE_SPECIFIER = "Navigation Example"; SWIFT_OBJC_BRIDGING_HEADER = "Example/Example-Swift-BridgingHeader.h"; @@ -3550,7 +3550,7 @@ "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.mapbox.Example-CarPlay"; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = Example; PROVISIONING_PROFILE = "69c90fd8-c53b-41a4-ac73-5bc11068a49a"; PROVISIONING_PROFILE_SPECIFIER = "Navigation Example"; SWIFT_OBJC_BRIDGING_HEADER = "Example/Example-Swift-BridgingHeader.h"; diff --git a/Sources/MapboxNavigation/CPMapTemplate.swift b/Sources/MapboxNavigation/CPMapTemplate.swift index 1c638f2b27a..bb80f163eb3 100644 --- a/Sources/MapboxNavigation/CPMapTemplate.swift +++ b/Sources/MapboxNavigation/CPMapTemplate.swift @@ -1,6 +1,5 @@ import Foundation import MapboxDirections -#if canImport(CarPlay) import CarPlay @available(iOS 12.0, *) @@ -32,5 +31,4 @@ extension CLLocationDirection { self = heading.wrap(min: 0, max: 360) } } -#endif diff --git a/Sources/MapboxNavigation/CPTrip.swift b/Sources/MapboxNavigation/CPTrip.swift index 3f92a18555e..95c0901e37a 100644 --- a/Sources/MapboxNavigation/CPTrip.swift +++ b/Sources/MapboxNavigation/CPTrip.swift @@ -1,7 +1,5 @@ import MapboxDirections -#if canImport(CarPlay) import CarPlay -#endif @available(iOS 12.0, *) extension CPTrip { diff --git a/Sources/MapboxNavigation/CarPlayManager.swift b/Sources/MapboxNavigation/CarPlayManager.swift index edfdf7d6a17..029b01d49be 100644 --- a/Sources/MapboxNavigation/CarPlayManager.swift +++ b/Sources/MapboxNavigation/CarPlayManager.swift @@ -1,4 +1,3 @@ -#if canImport(CarPlay) import CarPlay import MapboxCoreNavigation import MapboxDirections @@ -493,7 +492,10 @@ extension CarPlayManager: CPMapTemplateDelegate { currentNavigator = navigationViewController carPlayMapViewController.isOverviewingRoutes = false - carPlayMapViewController.present(navigationViewController, animated: true, completion: nil) + navigationViewController.modalPresentationStyle = .fullScreen + carPlayMapViewController.present(navigationViewController, animated: true) { + self.delegate?.carPlayManager(self, didPresent: navigationViewController) + } let mapView = carPlayMapViewController.mapView mapView.removeRoutes() @@ -689,7 +691,7 @@ extension CarPlayManager: CarPlayNavigationDelegate { interfaceController.setRootTemplate(mapTemplate, animated: true) popToRootTemplate(interfaceController: interfaceController, animated: true) - + delegate?.carPlayManagerDidEndNavigation(self) } } @@ -705,6 +707,51 @@ extension CarPlayManager: MapTemplateProviderDelegate { } } +@available(iOS 13.0, *) +extension CarPlayManager { + public func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, didConnectCarInterfaceController interfaceController: CPInterfaceController, to window: CPWindow) { + CarPlayManager.isConnected = true + interfaceController.delegate = self + self.interfaceController = interfaceController + + if let shouldDisableIdleTimer = delegate?.carplayManagerShouldDisableIdleTimer(self) { + UIApplication.shared.isIdleTimerDisabled = shouldDisableIdleTimer + } else { + UIApplication.shared.isIdleTimerDisabled = true + } + + let carPlayMapViewController = CarPlayMapViewController(styles: styles) + window.rootViewController = carPlayMapViewController + self.carWindow = window + + let mapTemplate = self.mapTemplate(for: interfaceController) + mainMapTemplate = mapTemplate + interfaceController.setRootTemplate(mapTemplate, animated: false) + + eventsManager.sendCarPlayConnectEvent() + } + + public func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, didDisconnectCarInterfaceController interfaceController: CPInterfaceController, from window: CPWindow) { + CarPlayManager.isConnected = false + self.interfaceController = nil + + window.rootViewController = nil + window.isHidden = true + window.removeFromSuperview() + + mainMapTemplate = nil + carWindow = nil + + eventsManager.sendCarPlayDisconnectEvent() + + if let shouldDisableIdleTimer = delegate?.carplayManagerShouldDisableIdleTimer(self) { + UIApplication.shared.isIdleTimerDisabled = !shouldDisableIdleTimer + } else { + UIApplication.shared.isIdleTimerDisabled = false + } + } +} + @available(iOS 12.0, *) internal protocol MapTemplateProviderDelegate: class { func mapTemplateProvider(_ provider: MapTemplateProvider, mapTemplate: CPMapTemplate, leadingNavigationBarButtonsCompatibleWith traitCollection: UITraitCollection, for activity: CarPlayActivity) -> [CPBarButton]? @@ -735,15 +782,3 @@ internal class MapTemplateProvider: NSObject { return CPMapTemplate() } } - -#else -/** - CarPlay support requires iOS 12.0 or above and the CarPlay framework. - */ -public class CarPlayManager: NSObject { - /** - A Boolean value indicating whether the phone is connected to CarPlay. - */ - public static var isConnected = false -} -#endif diff --git a/Sources/MapboxNavigation/CarPlayManagerDelegate.swift b/Sources/MapboxNavigation/CarPlayManagerDelegate.swift index 763b2af2afd..c31b7ae3a24 100644 --- a/Sources/MapboxNavigation/CarPlayManagerDelegate.swift +++ b/Sources/MapboxNavigation/CarPlayManagerDelegate.swift @@ -1,4 +1,3 @@ -#if canImport(CarPlay) import CarPlay import Turf import MapboxCoreNavigation @@ -153,6 +152,8 @@ public protocol CarPlayManagerDelegate: class, UnimplementedLogging { - returns: A Boolean value indicating whether to disable idle timer when carplay is connected and enable when disconnected. */ func carplayManagerShouldDisableIdleTimer(_ carPlayManager: CarPlayManager) -> Bool + + func carPlayManager(_ carPlayManager: CarPlayManager, didPresent navigationViewController: CarPlayNavigationViewController) -> () } @available(iOS 12.0, *) @@ -247,5 +248,11 @@ public extension CarPlayManagerDelegate { logUnimplemented(protocolType: CarPlayManagerDelegate.self, level: .debug) return false } + + /** + `UnimplementedLogging` prints a warning to standard output the first time this method is called. + */ + func carPlayManager(_ carPlayManager: CarPlayManager, didPresent navigationViewController: CarPlayNavigationViewController) { + logUnimplemented(protocolType: CarPlayManagerDelegate.self, level: .debug) + } } -#endif diff --git a/Sources/MapboxNavigation/CarPlayMapViewController.swift b/Sources/MapboxNavigation/CarPlayMapViewController.swift index 0f6cd3f9733..c0b49d97f8a 100644 --- a/Sources/MapboxNavigation/CarPlayMapViewController.swift +++ b/Sources/MapboxNavigation/CarPlayMapViewController.swift @@ -1,5 +1,4 @@ import Foundation -#if canImport(CarPlay) import CarPlay /** @@ -229,5 +228,4 @@ extension CarPlayMapViewController: StyleManagerDelegate { mapView.reloadStyle(self) } } -#endif diff --git a/Sources/MapboxNavigation/CarPlayNavigationViewController.swift b/Sources/MapboxNavigation/CarPlayNavigationViewController.swift index 0756ff8a99c..5e6b9a32c92 100644 --- a/Sources/MapboxNavigation/CarPlayNavigationViewController.swift +++ b/Sources/MapboxNavigation/CarPlayNavigationViewController.swift @@ -1,7 +1,6 @@ import Foundation import MapboxDirections import MapboxCoreNavigation -#if canImport(CarPlay) import CarPlay /** @@ -594,4 +593,3 @@ public extension CarPlayNavigationDelegate { //no-op, deprecated method } } -#endif diff --git a/Sources/MapboxNavigation/CarPlaySearchController+CPSearchTemplateDelegate.swift b/Sources/MapboxNavigation/CarPlaySearchController+CPSearchTemplateDelegate.swift index adb29c7eedc..8e30498099f 100644 --- a/Sources/MapboxNavigation/CarPlaySearchController+CPSearchTemplateDelegate.swift +++ b/Sources/MapboxNavigation/CarPlaySearchController+CPSearchTemplateDelegate.swift @@ -1,4 +1,4 @@ -#if canImport(CarPlay) && canImport(MapboxGeocoder) +#if canImport(MapboxGeocoder) import Foundation import CarPlay import MapboxGeocoder diff --git a/Sources/MapboxNavigation/CarPlaySearchController.swift b/Sources/MapboxNavigation/CarPlaySearchController.swift index c869e18f58d..08b02c1fcb7 100644 --- a/Sources/MapboxNavigation/CarPlaySearchController.swift +++ b/Sources/MapboxNavigation/CarPlaySearchController.swift @@ -1,4 +1,3 @@ -#if canImport(CarPlay) import CarPlay import MapboxDirections @@ -39,9 +38,3 @@ public class CarPlaySearchController: NSObject { */ public weak var delegate: CarPlaySearchControllerDelegate? } -#else -/** - CarPlay support requires iOS 12.0 or above and the CarPlay framework. - */ -public class CarPlaySearchController: NSObject {} -#endif diff --git a/Sources/MapboxNavigation/CongestionLevel.swift b/Sources/MapboxNavigation/CongestionLevel.swift index e9fa7c0315d..ed17409b842 100644 --- a/Sources/MapboxNavigation/CongestionLevel.swift +++ b/Sources/MapboxNavigation/CongestionLevel.swift @@ -1,6 +1,5 @@ import Foundation import MapboxDirections -#if canImport(CarPlay) import CarPlay extension CongestionLevel { @@ -23,4 +22,3 @@ extension CongestionLevel { } } } -#endif diff --git a/Sources/MapboxNavigation/DayStyle.swift b/Sources/MapboxNavigation/DayStyle.swift index b0fdb9f5470..cb99b8185b8 100644 --- a/Sources/MapboxNavigation/DayStyle.swift +++ b/Sources/MapboxNavigation/DayStyle.swift @@ -80,13 +80,11 @@ open class DayStyle: Style { Button.appearance().textColor = .defaultPrimaryText CancelButton.appearance().tintColor = .defaultPrimaryText - #if canImport(CarPlay) CarPlayCompassView.appearance().backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0.6022227113) CarPlayCompassView.appearance().cornerRadius = 4 CarPlayCompassView.appearance().borderWidth = 1.0 / (UIScreen.mainCarPlay?.scale ?? 2.0) CarPlayCompassView.appearance().borderColor = #colorLiteral(red: 0.09803921569, green: 0.09803921569, blue: 0.09803921569, alpha: 0.6009573063) - #endif - + DismissButton.appearance().backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) DismissButton.appearance().textColor = #colorLiteral(red: 0.09803921569, green: 0.09803921569, blue: 0.09803921569, alpha: 1) DismissButton.appearance().textFont = UIFont.systemFont(ofSize: 20, weight: .medium).adjustedFont @@ -135,6 +133,10 @@ open class DayStyle: Style { LaneView.appearance(whenContainedInInstancesOf: [LanesView.self]).secondaryColor = .defaultLaneArrowSecondary LaneView.appearance(whenContainedInInstancesOf: [LanesView.self]).primaryColorHighlighted = .defaultLaneArrowPrimaryHighlighted LaneView.appearance(whenContainedInInstancesOf: [LanesView.self]).secondaryColorHighlighted = .defaultLaneArrowSecondaryHighlighted + LaneView.appearance().primaryColor = .defaultLaneArrowPrimaryCarPlay + LaneView.appearance().secondaryColor = .defaultLaneArrowSecondaryCarPlay + LaneView.appearance().primaryColorHighlighted = .defaultLaneArrowPrimaryHighlighted + LaneView.appearance().secondaryColorHighlighted = .defaultLaneArrowSecondaryHighlighted LanesView.appearance().backgroundColor = #colorLiteral(red: 0.968627451, green: 0.968627451, blue: 0.968627451, alpha: 1) LineView.appearance().lineColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.1) ManeuverView.appearance().backgroundColor = .clear diff --git a/Sources/MapboxNavigation/NightStyle.swift b/Sources/MapboxNavigation/NightStyle.swift index 4db9f2850f8..5268853dcf7 100644 --- a/Sources/MapboxNavigation/NightStyle.swift +++ b/Sources/MapboxNavigation/NightStyle.swift @@ -20,9 +20,7 @@ open class NightStyle: DayStyle { Button.appearance().textColor = #colorLiteral(red: 0.9842069745, green: 0.9843751788, blue: 0.9841964841, alpha: 1) CancelButton.appearance().tintColor = #colorLiteral(red: 0.9842069745, green: 0.9843751788, blue: 0.9841964841, alpha: 1) - #if canImport(CarPlay) CarPlayCompassView.appearance().backgroundColor = backgroundColor - #endif DismissButton.appearance().backgroundColor = backgroundColor DismissButton.appearance().textColor = #colorLiteral(red: 0.9842069745, green: 0.9843751788, blue: 0.9841964841, alpha: 1) @@ -43,7 +41,10 @@ open class NightStyle: DayStyle { FloatingButton.appearance().tintColor = #colorLiteral(red: 0.9842069745, green: 0.9843751788, blue: 0.9841964841, alpha: 1) GenericRouteShield.appearance().foregroundColor = .white InstructionsBannerView.appearance().backgroundColor = backgroundColor - LaneView.appearance().primaryColor = #colorLiteral(red: 0.9842069745, green: 0.9843751788, blue: 0.9841964841, alpha: 1) + LaneView.appearance().primaryColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) + LaneView.appearance().secondaryColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0.3) + LaneView.appearance(whenContainedInInstancesOf: [LanesView.self]).primaryColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) + LaneView.appearance(whenContainedInInstancesOf: [LanesView.self]).secondaryColor = #colorLiteral(red: 0.4198532104, green: 0.4398920536, blue: 0.4437610507, alpha: 1) LanesView.appearance().backgroundColor = backgroundColor ManeuverView.appearance().backgroundColor = .clear ManeuverView.appearance(whenContainedInInstancesOf: [InstructionsBannerView.self]).primaryColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) diff --git a/Sources/MapboxNavigation/RecentItem.swift b/Sources/MapboxNavigation/RecentItem.swift index e118f4edf24..92dd4b53005 100644 --- a/Sources/MapboxNavigation/RecentItem.swift +++ b/Sources/MapboxNavigation/RecentItem.swift @@ -1,4 +1,4 @@ -#if canImport(CarPlay) && canImport(MapboxGeocoder) +#if canImport(MapboxGeocoder) import Foundation import MapboxGeocoder import CarPlay diff --git a/Sources/MapboxNavigation/UIScreen.swift b/Sources/MapboxNavigation/UIScreen.swift index 187261c8c92..d0a445d4a49 100644 --- a/Sources/MapboxNavigation/UIScreen.swift +++ b/Sources/MapboxNavigation/UIScreen.swift @@ -1,5 +1,4 @@ import Foundation -#if canImport(CarPlay) import CarPlay extension UIScreen { @@ -7,4 +6,3 @@ extension UIScreen { return UIScreen.screens.filter { $0.traitCollection.containsTraits(in: UITraitCollection(userInterfaceIdiom: .carPlay)) }.first } } -#endif diff --git a/Sources/MapboxNavigation/VisualInstruction.swift b/Sources/MapboxNavigation/VisualInstruction.swift index 479a2245ba0..a95e0e55a2b 100644 --- a/Sources/MapboxNavigation/VisualInstruction.swift +++ b/Sources/MapboxNavigation/VisualInstruction.swift @@ -1,7 +1,5 @@ import MapboxDirections -#if canImport(CarPlay) import CarPlay -#endif extension VisualInstruction { var laneComponents: [Component] { @@ -73,7 +71,6 @@ extension VisualInstruction { return newImage } - #if canImport(CarPlay) /// Returns a `CPImageSet` representing the maneuver. @available(iOS 12.0, *) public func maneuverImageSet(side: DrivingSide) -> CPImageSet? { @@ -172,5 +169,4 @@ extension VisualInstruction { } return nil } - #endif } diff --git a/Tests/MapboxNavigationTests/CPMapTemplateTests.swift b/Tests/MapboxNavigationTests/CPMapTemplateTests.swift index 86c026a17e4..4c28edff043 100644 --- a/Tests/MapboxNavigationTests/CPMapTemplateTests.swift +++ b/Tests/MapboxNavigationTests/CPMapTemplateTests.swift @@ -1,6 +1,5 @@ import XCTest @testable import MapboxNavigation -#if canImport(CarPlay) import CarPlay @available(iOS 12.0, *) @@ -20,4 +19,3 @@ class CPMapTemplateTests: XCTestCase { XCTAssertNil(CLLocationDirection(panDirection: [])) } } -#endif diff --git a/Tests/MapboxNavigationTests/CarPlayManagerTests.swift b/Tests/MapboxNavigationTests/CarPlayManagerTests.swift index c0756e19789..90601b116d8 100644 --- a/Tests/MapboxNavigationTests/CarPlayManagerTests.swift +++ b/Tests/MapboxNavigationTests/CarPlayManagerTests.swift @@ -5,7 +5,6 @@ import MapboxMobileEvents @testable import TestHelper @testable import MapboxNavigation -#if canImport(CarPlay) import CarPlay // For some reason XCTest bundles ignore @available annotations and these tests are run on iOS < 12 :( @@ -445,6 +444,10 @@ class CarPlayManagerSpec: QuickSpec { let directionsFake = Directions(credentials: Fixture.credentials) return MapboxNavigationService(route: route, routeIndex: routeIndex, routeOptions: routeOptions, directions: directionsFake, simulating: desiredSimulationMode) } + + func carPlayManager(_ carPlayManager: CarPlayManager, didPresent navigationViewController: CarPlayNavigationViewController) { + //no-op + } } } @@ -483,6 +486,10 @@ class CarPlayManagerFailureDelegateSpy: CarPlayManagerDelegate { func carPlayManagerDidEndNavigation(_ carPlayManager: CarPlayManager) { fatalError("This is an empty stub.") } + + func carPlayManager(_ carPlayManager: CarPlayManager, didPresent navigationViewController: CarPlayNavigationViewController) { + fatalError("This is an empty stub.") + } } //MARK: Test Objects / Classes. @@ -530,6 +537,10 @@ class TestCarPlayManagerDelegate: CarPlayManagerDelegate { navigationEnded = true currentService = nil } + + func carPlayManager(_ carPlayManager: CarPlayManager, didPresent navigationViewController: CarPlayNavigationViewController) { + XCTAssertTrue(navigationInitiated) + } } @available(iOS 12.0, *) @@ -657,4 +668,3 @@ class FakeCPInterfaceController: CPInterfaceController { } } } -#endif From 94c7e889d09da1f50baa91a129c8f21f97ab6591 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Thu, 8 Apr 2021 11:49:44 -0700 Subject: [PATCH 42/48] Minor tail work to add missing documentation for CarPlay changes in last commit (#2917) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Minor tail work to add missing documentation for CarPlay changes in last commit. * Update CHANGELOG.md Co-authored-by: Minh Nguyễn Co-authored-by: Minh Nguyễn --- CHANGELOG.md | 1 + .../xcshareddata/xcschemes/Example-CarPlay.xcscheme | 8 ++++---- Sources/MapboxNavigation/CarPlayManagerDelegate.swift | 8 ++++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d241ae426e4..74db062457a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * Added the `NavigationViewController.styleManager`, `StyleManager.currentStyleType`, and `StyleManager.currentStyle` properties and the `StyleManager.applyStyle(type:)` method to manually change the UI style at any time. ([#2888](https://github.com/mapbox/mapbox-navigation-ios/pull/2888)) * Fixed crash when switching between day / night modes ([#2896](https://github.com/mapbox/mapbox-navigation-ios/pull/2896)) * Add support for iOS 13's UIScene based CarPlay API ([#2832](https://github.com/mapbox/mapbox-navigation-ios/pull/2832)) +* Added an optional `CarPlayManagerDelegate.carPlayManager(_:didPresent:)` method that is called when `CarPlayManager` presents a new navigation session. ([#2832](https://github.com/mapbox/mapbox-navigation-ios/pull/2832)) ## v1.3.0 diff --git a/MapboxNavigation.xcodeproj/xcshareddata/xcschemes/Example-CarPlay.xcscheme b/MapboxNavigation.xcodeproj/xcshareddata/xcschemes/Example-CarPlay.xcscheme index f503cd26629..8cbcb7dbc12 100644 --- a/MapboxNavigation.xcodeproj/xcshareddata/xcschemes/Example-CarPlay.xcscheme +++ b/MapboxNavigation.xcodeproj/xcshareddata/xcschemes/Example-CarPlay.xcscheme @@ -15,7 +15,7 @@ @@ -31,7 +31,7 @@ @@ -54,7 +54,7 @@ @@ -71,7 +71,7 @@ diff --git a/Sources/MapboxNavigation/CarPlayManagerDelegate.swift b/Sources/MapboxNavigation/CarPlayManagerDelegate.swift index c31b7ae3a24..3b0ae8f3417 100644 --- a/Sources/MapboxNavigation/CarPlayManagerDelegate.swift +++ b/Sources/MapboxNavigation/CarPlayManagerDelegate.swift @@ -153,6 +153,14 @@ public protocol CarPlayManagerDelegate: class, UnimplementedLogging { */ func carplayManagerShouldDisableIdleTimer(_ carPlayManager: CarPlayManager) -> Bool + /** + Called when the CarPlayManager presents a new CarPlayNavigationViewController upon start of a navigation session. + + Implementing this method will allow developers to query or customize properties of the presented CarPlayNavigationViewController. For example, a developer may wish to perform custom map styling on the presented NavigationMapView. + + - parameter carPlayManager: The CarPlay manager instance. + - parameter navigationViewController: The CarPlayNavigationViewController that was presented on the CarPlay display. + */ func carPlayManager(_ carPlayManager: CarPlayManager, didPresent navigationViewController: CarPlayNavigationViewController) -> () } From fbb9f0c8ab832c18189b60085d7a882b4c552bb7 Mon Sep 17 00:00:00 2001 From: Alexander Pristavko Date: Fri, 9 Apr 2021 19:04:49 +0300 Subject: [PATCH 43/48] Release v1.4 (#2919) * Update translations * Refine changelog * Update version to 1.4.0 * Adjust project paths in localization script * Fix localization files encoding --- .gitattributes | 1 + Bench/Info.plist | 4 +- BenchTests/Info.plist | 4 +- CHANGELOG.md | 8 +- Example/Info.plist | 4 +- MapboxCoreNavigation.podspec | 2 +- MapboxNavigation-Documentation.podspec | 2 +- MapboxNavigation.podspec | 2 +- MapboxNavigation.xcodeproj/project.pbxproj | 20 +- README.md | 4 +- Sources/MapboxCoreNavigation/Info.plist | 4 +- .../Resources/ca.lproj/Localizable.strings | 9 +- Sources/MapboxNavigation/Info.plist | 4 +- .../Resources/ca.lproj/Localizable.strings | 207 +++++++++++++++++- .../Resources/ca.lproj/Navigation.strings | 8 +- .../Resources/de.lproj/Localizable.strings | 12 + .../Resources/es.lproj/Localizable.strings | 12 + .../Resources/uk.lproj/Localizable.strings | 6 + .../Resources/vi.lproj/Localizable.strings | 12 + Tests/CocoaPodsTest/PodInstall/Podfile.lock | 12 +- Tests/MapboxCoreNavigationTests/Info.plist | 4 +- Tests/MapboxNavigationTests/Info.plist | 4 +- Tests/TestHelper/Info.plist | 4 +- custom-navigation.md | 10 +- scripts/convert_string_files.sh | 2 +- 25 files changed, 305 insertions(+), 56 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..f4ed7ca701a --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.strings diff diff --git a/Bench/Info.plist b/Bench/Info.plist index 70792cc5569..c5e37331c6d 100644 --- a/Bench/Info.plist +++ b/Bench/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.0 + 1.4.0 CFBundleVersion - 44 + 45 LSRequiresIPhoneOS NSLocationAlwaysAndWhenInUseUsageDescription diff --git a/BenchTests/Info.plist b/BenchTests/Info.plist index 819f0a2af56..298db75182c 100644 --- a/BenchTests/Info.plist +++ b/BenchTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.0 + 1.4.0 CFBundleVersion - 44 + 45 diff --git a/CHANGELOG.md b/CHANGELOG.md index 74db062457a..d546b2f58a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,13 @@ ## v1.4.0 * Increased the minimum version of `MapboxNavigationNative` to v32.0.0. ([#2910](https://github.com/mapbox/mapbox-navigation-ios/pull/2910)) -* Fixed an issue when feedback UI was always appearing for short routes ([#2871](https://github.com/mapbox/mapbox-navigation-ios/pull/2871)) -* Fixed automatic day/night switching ([#2881](https://github.com/mapbox/mapbox-navigation-ios/pull/2881)) +* Fixed an issue when feedback UI was always appearing for short routes. ([#2871](https://github.com/mapbox/mapbox-navigation-ios/pull/2871)) +* Fixed automatic day/night switching. ([#2881](https://github.com/mapbox/mapbox-navigation-ios/pull/2881)) * Fixed an issue where presenting `NavigationViewController` could sometimes interfere with view presentation in other windows. ([#2897](https://github.com/mapbox/mapbox-navigation-ios/pull/2897)) * Added an optional `StyleManagerDelegate.styleManager(_:viewForApplying:)` method to determine which part of the view hierarchy is affected by a change to a different `Style`. ([#2897](https://github.com/mapbox/mapbox-navigation-ios/pull/2897)) * Added the `NavigationViewController.styleManager`, `StyleManager.currentStyleType`, and `StyleManager.currentStyle` properties and the `StyleManager.applyStyle(type:)` method to manually change the UI style at any time. ([#2888](https://github.com/mapbox/mapbox-navigation-ios/pull/2888)) -* Fixed crash when switching between day / night modes ([#2896](https://github.com/mapbox/mapbox-navigation-ios/pull/2896)) -* Add support for iOS 13's UIScene based CarPlay API ([#2832](https://github.com/mapbox/mapbox-navigation-ios/pull/2832)) +* Fixed crash when switching between day / night modes. ([#2896](https://github.com/mapbox/mapbox-navigation-ios/pull/2896)) +* Added support for iOS 13's UIScene based CarPlay API. ([#2832](https://github.com/mapbox/mapbox-navigation-ios/pull/2832)) * Added an optional `CarPlayManagerDelegate.carPlayManager(_:didPresent:)` method that is called when `CarPlayManager` presents a new navigation session. ([#2832](https://github.com/mapbox/mapbox-navigation-ios/pull/2832)) ## v1.3.0 diff --git a/Example/Info.plist b/Example/Info.plist index b2ad20b39c7..7115587240e 100644 --- a/Example/Info.plist +++ b/Example/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.0 + 1.4.0 CFBundleVersion - 44 + 45 LSApplicationCategoryType LSRequiresIPhoneOS diff --git a/MapboxCoreNavigation.podspec b/MapboxCoreNavigation.podspec index 4785c44688d..36ecaaf5f6d 100644 --- a/MapboxCoreNavigation.podspec +++ b/MapboxCoreNavigation.podspec @@ -3,7 +3,7 @@ Pod::Spec.new do |s| # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # s.name = "MapboxCoreNavigation" - s.version = '1.3.0' + s.version = '1.4.0' s.summary = "Core components for turn-by-turn navigation on iOS." s.description = <<-DESC diff --git a/MapboxNavigation-Documentation.podspec b/MapboxNavigation-Documentation.podspec index ebe43efd307..ce0a388dac2 100644 --- a/MapboxNavigation-Documentation.podspec +++ b/MapboxNavigation-Documentation.podspec @@ -3,7 +3,7 @@ Pod::Spec.new do |s| # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # s.name = "MapboxNavigation-Documentation" - s.version = '1.3.0' + s.version = '1.4.0' s.summary = "Complete turn-by-turn navigation interface for iOS." s.description = <<-DESC diff --git a/MapboxNavigation.podspec b/MapboxNavigation.podspec index a3537f2f423..c92586dc61d 100644 --- a/MapboxNavigation.podspec +++ b/MapboxNavigation.podspec @@ -3,7 +3,7 @@ Pod::Spec.new do |s| # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # s.name = "MapboxNavigation" - s.version = '1.3.0' + s.version = '1.4.0' s.summary = "Complete turn-by-turn navigation interface for iOS." s.description = <<-DESC diff --git a/MapboxNavigation.xcodeproj/project.pbxproj b/MapboxNavigation.xcodeproj/project.pbxproj index a4930e50399..04108803458 100644 --- a/MapboxNavigation.xcodeproj/project.pbxproj +++ b/MapboxNavigation.xcodeproj/project.pbxproj @@ -3164,7 +3164,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_TEAM = GJZR2MEM28; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 44; + DYLIB_CURRENT_VERSION = 45; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3190,7 +3190,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_TEAM = GJZR2MEM28; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 44; + DYLIB_CURRENT_VERSION = 45; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3435,12 +3435,12 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 44; + CURRENT_PROJECT_VERSION = 45; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = GJZR2MEM28; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 44; + DYLIB_CURRENT_VERSION = 45; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3474,11 +3474,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 44; + CURRENT_PROJECT_VERSION = 45; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = GJZR2MEM28; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 44; + DYLIB_CURRENT_VERSION = 45; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3591,7 +3591,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 44; + CURRENT_PROJECT_VERSION = 45; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -3656,7 +3656,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 44; + CURRENT_PROJECT_VERSION = 45; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3690,7 +3690,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_TEAM = GJZR2MEM28; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 44; + DYLIB_CURRENT_VERSION = 45; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3719,7 +3719,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_TEAM = GJZR2MEM28; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 44; + DYLIB_CURRENT_VERSION = 45; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/README.md b/README.md index 54ef74773c6..b372ccc4174 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ To install Mapbox Navigation using [CocoaPods](https://cocoapods.org/): 1. Create a [Podfile](https://guides.cocoapods.org/syntax/podfile.html) with the following specification: ```ruby # Latest stable release - pod 'MapboxNavigation', '~> 1.3' + pod 'MapboxNavigation', '~> 1.4' # Latest prerelease pod 'MapboxCoreNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v1.3.0-beta.1' pod 'MapboxNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v1.3.0-beta.1' @@ -76,7 +76,7 @@ To install Mapbox Navigation using [Carthage](https://github.com/Carthage/Cartha 1. Create a [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#github-repositories) with the following dependency: ```cartfile # Latest stable release - github "mapbox/mapbox-navigation-ios" ~> 1.3 + github "mapbox/mapbox-navigation-ios" ~> 1.4 # Latest prerelease github "mapbox/mapbox-navigation-ios" "v1.3.0-beta.1" ``` diff --git a/Sources/MapboxCoreNavigation/Info.plist b/Sources/MapboxCoreNavigation/Info.plist index b392502bfb2..0bf61b30517 100644 --- a/Sources/MapboxCoreNavigation/Info.plist +++ b/Sources/MapboxCoreNavigation/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.3.0 + 1.4.0 CFBundleVersion - 44 + 45 NSPrincipalClass diff --git a/Sources/MapboxCoreNavigation/Resources/ca.lproj/Localizable.strings b/Sources/MapboxCoreNavigation/Resources/ca.lproj/Localizable.strings index 1728c7d2150..f39f758231c 100644 --- a/Sources/MapboxCoreNavigation/Resources/ca.lproj/Localizable.strings +++ b/Sources/MapboxCoreNavigation/Resources/ca.lproj/Localizable.strings @@ -1,5 +1,6 @@ -/* Format for speech string after completing a maneuver and starting a new step; 1 = distance */ -"CONTINUE" = "Continueu per %@"; +/* Error message when an offline route request returns a response that can’t be deserialized */ +"OFFLINE_CORRUPT_DATA" = "S'ha trobat una ruta no vàlida mentre fora de línia."; + +/* Inform developer an update is available */ +"UPDATE_AVAILABLE" = "Disponible la versió %@ de Mapbox Navigation SDK per iOS"; -/* Format for speech string after completing a maneuver and starting a new step; 1 = way name; 2 = distance */ -"CONTINUE_ON_ROAD" = "Continueu a %1$@ per %2$@"; diff --git a/Sources/MapboxNavigation/Info.plist b/Sources/MapboxNavigation/Info.plist index b392502bfb2..0bf61b30517 100644 --- a/Sources/MapboxNavigation/Info.plist +++ b/Sources/MapboxNavigation/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.3.0 + 1.4.0 CFBundleVersion - 44 + 45 NSPrincipalClass diff --git a/Sources/MapboxNavigation/Resources/ca.lproj/Localizable.strings b/Sources/MapboxNavigation/Resources/ca.lproj/Localizable.strings index 9d154976f31..5dba325bbb8 100644 --- a/Sources/MapboxNavigation/Resources/ca.lproj/Localizable.strings +++ b/Sources/MapboxNavigation/Resources/ca.lproj/Localizable.strings @@ -1,26 +1,231 @@ +/* Delimiter between lines in an address when displayed inline */ +"ADDRESS_LINE_SEPARATOR" = ","; + +/* Title on arrival action sheet */ +"CARPLAY_ARRIVED" = "Heu arribat"; + +/* Message on arrival action sheet */ +"CARPLAY_ARRIVED_MESSAGE" = "Que us agradaria fer? "; + +/* Name of the waypoint associated with the current location */ +"CARPLAY_CURRENT_LOCATION" = "Posició actual"; + +/* Title for dismiss button */ +"CARPLAY_DISMISS" = "Descartar"; + +/* Title for end navigation button */ +"CARPLAY_END" = "Fi"; + +/* Title on the exit button in the arrival form */ +"CARPLAY_EXIT_NAVIGATION" = "Sortir de la navegació"; + +/* Title for feedback template in CarPlay */ +"CARPLAY_FEEDBACK" = "Comentaris"; + +/* Title for start button in CPTripPreviewTextConfiguration */ +"CARPLAY_GO" = "Endavant"; + +/* Title for alternative routes in CPTripPreviewTextConfiguration */ +"CARPLAY_MORE_ROUTES" = "Més rutes"; + +/* Title for mute button */ +"CARPLAY_MUTE" = "Silenciar"; + +/* Title for overview button in CPTripPreviewTextConfiguration */ +"CARPLAY_OVERVIEW" = "Visió General"; + +/* Title for rating template in CarPlay */ +"CARPLAY_RATE_RIDE" = "Valora el viatge"; + +/* Title on rate button in CarPlay */ +"CARPLAY_RATE_TRIP" = "Valora el viatge"; + +/* Message when search returned zero results in CarPlay */ +"CARPLAY_SEARCH_NO_RESULTS" = "Sense resultats"; + +/* Alert title that shows when feedback has been submitted */ +"CARPLAY_SUBMITTED_FEEDBACK" = "Enviat"; + +/* Title for unmute button */ +"CARPLAY_UNMUTE" = "Deixar de silenciar"; + +/* Specific route feedback that audio guidance provided was confusing. */ +"CONFUSING_AUDIO_FEEDBACK" = "Àudio confús"; + +/* Specific route feedback that audio guidance was provided too early before a maneuever. */ +"CONFUSING_AUDIO_GUIDANCE_TOO_EARLY_FEEDBACK" = "Orientació massa aviat"; + +/* Specific route feedback that audio guidance was provided too late for a maneuever. */ +"CONFUSING_AUDIO_GUIDANCE_TOO_LATE_FEEDBACK" = "Orientació massa tard"; + +/* Specific route feedback that an audio guidance problem was encountered but not listed as a choice. */ +"CONFUSING_AUDIO_OTHER_FEEDBACK" = "Altres"; + +/* Specific route feedback that audio guidance used incorrect pronunciation. */ +"CONFUSING_AUDIO_PRONUNCIATION_INCORRECT_FEEDBACK" = "Pronunciació incorrecta"; + +/* Specific route feedback that audio guidance repeated a road name. */ +"CONFUSING_AUDIO_ROADNAME_REPEATED_FEEDBACK" = "Nom de vial repetit"; + +/* Dismiss button title on the steps view */ +"DISMISS_STEPS_TITLE" = "Tancar"; + +/* Label indicating precise location is off and needs to be turned on to navigate */ +"ENABLE_PRECISE_LOCATION" = "Activeu la localització precisa per navegar"; + +/* Title used for arrival */ +"END_OF_ROUTE_ARRIVED" = "Heu arribatHeu arribat"; + +/* Comment Placeholder Text */ +"END_OF_ROUTE_TITLE" = "Com podem millorar?"; + /* Indicates a faster route was found */ "FASTER_ROUTE_FOUND" = "S’ha trobat una ruta més ràpida"; /* Message confirming that the user has successfully sent feedback */ "FEEDBACK_THANK_YOU" = "Gràcies pel vostre informe!"; +/* Title of view controller for sending feedback */ +"FEEDBACK_TITLE" = "Informar d'un problema"; + +/* Specific route feedback that a illegal route problem was encountered but not listed as a choice. */ +"ILLEGAL_ROUTE_OTHER_FEEDBACK" = "Altres"; + +/* Label indicating the device volume is too low to hear spoken instructions and needs to be manually increased */ +"INAUDIBLE_INSTRUCTIONS_CTA" = "Ajusteu el volum per sentir les instruccions"; + +/* Specific route feedback that a speed limit is incorrect. */ +"INCORRECT_SPEED_LIMIT" = "Límit de velocitat incorrecte"; + +/* Specific route feedback that an exit was incorrect. */ +"INCORRECT_VISUAL_EXIT_INFO_INCORRECT_FEEDBACK" = "Informació de sortida incorrecta"; + +/* General category of route feedback where visual instruction was incorrect. */ +"INCORRECT_VISUAL_FEEDBACK" = "Sembla incorrecte"; + +/* Specific route feedback that an instruction was missing. */ +"INCORRECT_VISUAL_INSTRUCTION_MISSING_FEEDBACK" = "Falten instruccions"; + +/* Specific route feedback for an unnecessary instruction. */ +"INCORRECT_VISUAL_INSTRUCTION_UNNECESSARY_FEEDBACK" = "Instruccions innecessàries"; + +/* Specific route feedback that the wrong lane was specified. */ +"INCORRECT_VISUAL_LANE_GUIDANCE_INCORRECT_FEEDBACK" = "Guia de carril incorrecta"; + +/* Specific route feedback that a maneuver specified was incorrect. */ +"INCORRECT_VISUAL_MANEUVER_INCORRECT_FEEDBACK" = "Maniobra incorrecta"; + +/* Specific route feedback that a visual instruction problem was encountered but not listed as a choice. */ +"INCORRECT_VISUAL_OTHER_FEEDBACK" = "Altres"; + +/* Specific route feedback that a road is known by another name. */ +"INCORRECT_VISUAL_ROAD_NAME_DIFFERENT_FEEDBACK" = "Carretera coneguda amb un nom diferent"; + +/* Specific route feedback for incorrect street name. */ +"INCORRECT_VISUAL_STREET_NAME_INCORRECT_FEEDBACK" = "Nom de carrer incorrecte"; + +/* Specific route feedback for incorrect turn arrow being shown. */ +"INCORRECT_VISUAL_TURN_ICON_INCORRECT_FEEDBACK" = "Icona de gir incorrecta"; + /* Format for displaying the first two major ways */ "LEG_MAJOR_WAYS_FORMAT" = "%1$@ i %2$@"; /* Format string for a short distance or time less than a minimum threshold; 1 = duration remaining */ "LESS_THAN" = "<%@"; +/* Title for button that cancels user's submission of feedback on navigation session issues. */ +"NAVIGATION_REPORT_CANCEL" = "Cancel·lar"; + +/* Title for button that submits user's feedback on multiple navigation session issues. 1 is the number of items */ +"NAVIGATION_REPORT_ISSUES" = "Enviar %ld article(s)"; + +/* Accessibility value of label indicating the absence of a rating */ +"NO_RATING" = "Sense valoració."; + +/* General category of route feedback where user position is incorrect. */ +"POSITIONING" = "Posicionament"; + +/* Specific route feedback that user positioning is incorrect. */ +"POSITIONING_USER" = "La vostra posició"; + +/* Rating Reset To Zero Accessability Hint */ +"RATING_ACCESSIBILITY_RESET" = "Toca per restablir la valoració a zero."; + +/* Format for accessibility label of button for setting a rating; 1 = number of stars */ +"RATING_ACCESSIBILITY_SET" = "Estableix una puntuació de %ld-estels"; + +/* Format for accessibility value of label indicating the existing rating; 1 = number of stars */ +"RATING_STARS_FORMAT" = "%ld estel(s) afegits."; + /* Indicates that rerouting is in progress */ "REROUTING" = "Recalculant la ruta…"; /* Button title for resume tracking */ "RESUME" = "Continuar"; +/* Specific route feedback that a road closure type was encountered but not listed as a choice. */ +"ROAD_CLOSURE_OTHER_FEEDBACK" = "Altres"; + +/* This route does not have tolls */ +"ROUTE_HAS_NO_TOLLS" = "Sense peatges"; + +/* This route does have tolls */ +"ROUTE_HAS_TOLLS" = "Peatges"; + +/* Specific route feedback that user was offered an alternative that was unexpected. */ +"ROUTE_QUALITY_ALTERNATIVE_ROUTE_NOT_EXPECTED_FEEDBACK" = "No s’espera una ruta alternativa"; + +/* General category of route feedback where route quality was poor. */ +"ROUTE_QUALITY_FEEDBACK" = "Qualitat de la ruta"; + +/* Specific route feedback that route suggested an illegal roadway. */ +"ROUTE_QUALITY_ILLEGAL_ROUTE_CARS_NOT_ALLOWED_FEEDBACK" = "No es permeten cotxes al carrer"; + +/* General route feedback that route contained illegal instructions. */ +"ROUTE_QUALITY_ILLEGAL_ROUTE_FEEDBACK" = "Ruta il·legal"; + +/* Specific route feedback that route travelled wrong way on a one-way road. */ +"ROUTE_QUALITY_ILLEGAL_ROUTE_ONE_WAY_FEEDBACK" = "Dirigit cap a un vial de sentit únic"; + +/* Specific route feedback that route suggested an illegal turn. */ +"ROUTE_QUALITY_ILLEGAL_ROUTE_TURN_NOT_ALLOWED_FEEDBACK" = "No es podia girar"; + +/* Specific route feedback that route suggested an illegal unprotected turn. */ +"ROUTE_QUALITY_ILLEGAL_ROUTE_UNPROTECTED_TURN_FEEDBACK" = "Gir desprotegit a la intersecció"; + +/* Specific route feedback that route contained non-existant roads. */ +"ROUTE_QUALITY_INCLUDED_MISSING_ROADS_FEEDBACK" = "La ruta inclou carreteres que falten"; + +/* Specific route feedback that route was not drivable. */ +"ROUTE_QUALITY_NON_DRIVABLE_FEEDBACK" = "No es pot conduïr per aquesta ruta"; + +/* Specific route feedback that route was not ideal according to user. */ +"ROUTE_QUALITY_NOT_PREFERRED_FEEDBACK" = "Aquesta ruta no és la preferida"; + +/* Specific route feedback that a route quality problem was encountered but not listed as a choice. */ +"ROUTE_QUALITY_OTHER_FEEDBACK" = "Altres"; + +/* General route feedback that route contained closed road. */ +"ROUTE_QUALITY_ROAD_CLOSURE_FEEDBACK" = "Carretera tancada"; + +/* Specifc route feedback that route contained road that is permanently closed. */ +"ROUTE_QUALITY_ROAD_CLOSURE_PERMANENT_FEEDBACK" = "Carrer bloquejat permanentment"; + +/* Specifc route feedback that route contained road not shown on map. */ +"ROUTE_QUALITY_ROAD_MISSING_FROM_MAP_FEEDBACK" = "Falta la carretera al mapa"; + +/* Specific route feedback that route contained impassible, narrow roads. */ +"ROUTE_QUALITY_ROADS_TOO_NARROW_FEEDBACK" = "La ruta tenia passos massa estrets"; + +/* Label above the speed limit in an MUTCD-style speed limit sign. Keep as short as possible. */ +"SPEED_LIMIT_LEGEND" = "Màx"; + /* The text of a banner that appears during turn-by-turn navigation when route simulation is enabled. */ -"USER_IN_SIMULATION_MODE" = "Simular la navegació"; +"USER_IN_SIMULATION_MODE" = "Simular navegació a %@"; /* Format for displaying destination and intermediate waypoints; 1 = source ; 2 = destinations */ "WAYPOINT_DESTINATION_VIA_WAYPOINTS_FORMAT" = "%1$@, via %2$@"; /* Format for displaying start and endpoint for leg; 1 = source ; 2 = destination */ "WAYPOINT_SOURCE_DESTINATION_FORMAT" = "%1$@ i %2$@"; + diff --git a/Sources/MapboxNavigation/Resources/ca.lproj/Navigation.strings b/Sources/MapboxNavigation/Resources/ca.lproj/Navigation.strings index 97ffa32f745..a48c493c72f 100644 --- a/Sources/MapboxNavigation/Resources/ca.lproj/Navigation.strings +++ b/Sources/MapboxNavigation/Resources/ca.lproj/Navigation.strings @@ -1,6 +1,6 @@ -/* Class = "UILabel"; text = "Rerouting…"; ObjectID = "UpZ-gr-OKM"; */ -"UpZ-gr-OKM.text" = "Recalculant la ruta…"; +/* Class = "UIButton"; normalTitle = "End Navigation"; ObjectID = "5f2-dT-la4"; */ +"5f2-dT-la4.normalTitle" = "Finalitza la navegació"; -/* Class = "UILabel"; text = "Recording Audio"; ObjectID = "xTH-eK-F5H"; */ -"xTH-eK-F5H.text" = "Enregistrant àudio"; +/* Class = "UILabel"; text = "Rate your trip"; ObjectID = "W5U-cV-cDO"; */ +"W5U-cV-cDO.text" = "Valora el viatge"; diff --git a/Sources/MapboxNavigation/Resources/de.lproj/Localizable.strings b/Sources/MapboxNavigation/Resources/de.lproj/Localizable.strings index 368c2c8c93e..c5e4bf99d5e 100644 --- a/Sources/MapboxNavigation/Resources/de.lproj/Localizable.strings +++ b/Sources/MapboxNavigation/Resources/de.lproj/Localizable.strings @@ -70,6 +70,9 @@ /* Dismiss button title on the steps view */ "DISMISS_STEPS_TITLE" = "Schließen"; +/* Label indicating precise location is off and needs to be turned on to navigate */ +"ENABLE_PRECISE_LOCATION" = "Enable precise location to navigate"; + /* Title used for arrival */ "END_OF_ROUTE_ARRIVED" = "Du bist angekommen"; @@ -91,6 +94,9 @@ /* Label indicating the device volume is too low to hear spoken instructions and needs to be manually increased */ "INAUDIBLE_INSTRUCTIONS_CTA" = "Lautstärke anpassen, um Anweisungen zu hören"; +/* Specific route feedback that a speed limit is incorrect. */ +"INCORRECT_SPEED_LIMIT" = "Speed limit incorrect"; + /* Specific route feedback that an exit was incorrect. */ "INCORRECT_VISUAL_EXIT_INFO_INCORRECT_FEEDBACK" = "Ausfahrtsinformation falsch"; @@ -160,6 +166,12 @@ /* Specific route feedback that a road closure type was encountered but not listed as a choice. */ "ROAD_CLOSURE_OTHER_FEEDBACK" = "Anderes"; +/* This route does not have tolls */ +"ROUTE_HAS_NO_TOLLS" = "No Tolls"; + +/* This route does have tolls */ +"ROUTE_HAS_TOLLS" = "Tolls"; + /* Specific route feedback that user was offered an alternative that was unexpected. */ "ROUTE_QUALITY_ALTERNATIVE_ROUTE_NOT_EXPECTED_FEEDBACK" = "Alternative Route war unerwartet"; diff --git a/Sources/MapboxNavigation/Resources/es.lproj/Localizable.strings b/Sources/MapboxNavigation/Resources/es.lproj/Localizable.strings index 6ad13f2edc2..91dfcf279bd 100644 --- a/Sources/MapboxNavigation/Resources/es.lproj/Localizable.strings +++ b/Sources/MapboxNavigation/Resources/es.lproj/Localizable.strings @@ -70,6 +70,9 @@ /* Dismiss button title on the steps view */ "DISMISS_STEPS_TITLE" = "Cerrar"; +/* Label indicating precise location is off and needs to be turned on to navigate */ +"ENABLE_PRECISE_LOCATION" = "Activar la ubicación precisa para navigar"; + /* Title used for arrival */ "END_OF_ROUTE_ARRIVED" = "Ha llegado"; @@ -91,6 +94,9 @@ /* Label indicating the device volume is too low to hear spoken instructions and needs to be manually increased */ "INAUDIBLE_INSTRUCTIONS_CTA" = "Ajustar el volumen para escuchar las instrucciones"; +/* Specific route feedback that a speed limit is incorrect. */ +"INCORRECT_SPEED_LIMIT" = "Límite de velocidad incorrecto"; + /* Specific route feedback that an exit was incorrect. */ "INCORRECT_VISUAL_EXIT_INFO_INCORRECT_FEEDBACK" = "Detalles de la salida incorrectos"; @@ -160,6 +166,12 @@ /* Specific route feedback that a road closure type was encountered but not listed as a choice. */ "ROAD_CLOSURE_OTHER_FEEDBACK" = "Otro"; +/* This route does not have tolls */ +"ROUTE_HAS_NO_TOLLS" = "No Tolls"; + +/* This route does have tolls */ +"ROUTE_HAS_TOLLS" = "Tolls"; + /* Specific route feedback that user was offered an alternative that was unexpected. */ "ROUTE_QUALITY_ALTERNATIVE_ROUTE_NOT_EXPECTED_FEEDBACK" = "Ruta alternativa inesperada"; diff --git a/Sources/MapboxNavigation/Resources/uk.lproj/Localizable.strings b/Sources/MapboxNavigation/Resources/uk.lproj/Localizable.strings index e370af3efae..3f9c7d78287 100644 --- a/Sources/MapboxNavigation/Resources/uk.lproj/Localizable.strings +++ b/Sources/MapboxNavigation/Resources/uk.lproj/Localizable.strings @@ -166,6 +166,12 @@ /* Specific route feedback that a road closure type was encountered but not listed as a choice. */ "ROAD_CLOSURE_OTHER_FEEDBACK" = "Інше"; +/* This route does not have tolls */ +"ROUTE_HAS_NO_TOLLS" = "No Tolls"; + +/* This route does have tolls */ +"ROUTE_HAS_TOLLS" = "Tolls"; + /* Specific route feedback that user was offered an alternative that was unexpected. */ "ROUTE_QUALITY_ALTERNATIVE_ROUTE_NOT_EXPECTED_FEEDBACK" = "Неочікуваний альтернативний маршрут"; diff --git a/Sources/MapboxNavigation/Resources/vi.lproj/Localizable.strings b/Sources/MapboxNavigation/Resources/vi.lproj/Localizable.strings index e0940807a4c..01b2a210fad 100644 --- a/Sources/MapboxNavigation/Resources/vi.lproj/Localizable.strings +++ b/Sources/MapboxNavigation/Resources/vi.lproj/Localizable.strings @@ -70,6 +70,9 @@ /* Dismiss button title on the steps view */ "DISMISS_STEPS_TITLE" = "Đóng"; +/* Label indicating precise location is off and needs to be turned on to navigate */ +"ENABLE_PRECISE_LOCATION" = "Bật vị trí chính xác để điều hướng"; + /* Title used for arrival */ "END_OF_ROUTE_ARRIVED" = "Đã đến nơi rồi"; @@ -91,6 +94,9 @@ /* Label indicating the device volume is too low to hear spoken instructions and needs to be manually increased */ "INAUDIBLE_INSTRUCTIONS_CTA" = "Điều chỉnh Âm lượng để Nghe Hướng dẫn"; +/* Specific route feedback that a speed limit is incorrect. */ +"INCORRECT_SPEED_LIMIT" = "Tốc độ tối đa không chính xác"; + /* Specific route feedback that an exit was incorrect. */ "INCORRECT_VISUAL_EXIT_INFO_INCORRECT_FEEDBACK" = "Chi tiết đường nhánh sai"; @@ -160,6 +166,12 @@ /* Specific route feedback that a road closure type was encountered but not listed as a choice. */ "ROAD_CLOSURE_OTHER_FEEDBACK" = "Khác"; +/* This route does not have tolls */ +"ROUTE_HAS_NO_TOLLS" = "No Tolls"; + +/* This route does have tolls */ +"ROUTE_HAS_TOLLS" = "Tolls"; + /* Specific route feedback that user was offered an alternative that was unexpected. */ "ROUTE_QUALITY_ALTERNATIVE_ROUTE_NOT_EXPECTED_FEEDBACK" = "Tuyến đường thay thế bất ngờ"; diff --git a/Tests/CocoaPodsTest/PodInstall/Podfile.lock b/Tests/CocoaPodsTest/PodInstall/Podfile.lock index 76649c13e3c..eaa9deaf74f 100644 --- a/Tests/CocoaPodsTest/PodInstall/Podfile.lock +++ b/Tests/CocoaPodsTest/PodInstall/Podfile.lock @@ -3,7 +3,7 @@ PODS: - MapboxMobileEvents (~> 0.10.4) - MapboxAccounts (2.3.1) - MapboxCommon (9.2.0) - - MapboxCoreNavigation (1.3.0): + - MapboxCoreNavigation (1.4.0): - MapboxAccounts (~> 2.3.0) - MapboxDirections (~> 1.2.0) - MapboxMobileEvents (~> 0.10.2) @@ -13,9 +13,9 @@ PODS: - Polyline (~> 5.0) - Turf (~> 1.0) - MapboxMobileEvents (0.10.8) - - MapboxNavigation (1.3.0): + - MapboxNavigation (1.4.0): - Mapbox-iOS-SDK (~> 6.0) - - MapboxCoreNavigation (= 1.3.0) + - MapboxCoreNavigation (= 1.4.0) - MapboxMobileEvents (~> 0.10.2) - MapboxSpeech (~> 1.0) - Solar (~> 2.1) @@ -50,13 +50,13 @@ EXTERNAL SOURCES: :path: "../../../" SPEC CHECKSUMS: - Mapbox-iOS-SDK: 2563ed87ead6ec08f1c2090873977b8a7be335a8 + Mapbox-iOS-SDK: 6a67397df2bcafe5d2851604192aa8de6f8d6c89 MapboxAccounts: e40ef575df5d8b7ef33d0504ff2d393f4fde0455 MapboxCommon: 2c4ef00c10cba35302835423f4d63c6f3dea4bc4 - MapboxCoreNavigation: 5733798e2747310ffd410105420ef5dc64d02620 + MapboxCoreNavigation: 7506530a8ac5c55af12a84e1c6060bf9c6e6514a MapboxDirections: 383df0cd65784897c2269750a42d9654c832f0ce MapboxMobileEvents: 36ff53b135aac486eed94b61f813c7967a0c2c6f - MapboxNavigation: 222274f1330cf9336529ade519a14df4042dfd9f + MapboxNavigation: 3c2bcadb9c1ce419f23f6d2f5a8cce2254d47231 MapboxNavigationNative: e811b9beed18c01e4693e29f843d36b4a760fa61 MapboxSpeech: 4b3aea42e35d056fae1d7ad847a9fc0f412d911e Polyline: fce41d72e1146c41c6d081f7656827226f643dff diff --git a/Tests/MapboxCoreNavigationTests/Info.plist b/Tests/MapboxCoreNavigationTests/Info.plist index c17d7352871..793ed1375e8 100644 --- a/Tests/MapboxCoreNavigationTests/Info.plist +++ b/Tests/MapboxCoreNavigationTests/Info.plist @@ -15,12 +15,12 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.0 + 1.4.0 NSLocationWhenInUseUsageDescription Location Usage Description NSLocationAlwaysAndWhenInUseUsageDescription Location Usage Description CFBundleVersion - 44 + 45 diff --git a/Tests/MapboxNavigationTests/Info.plist b/Tests/MapboxNavigationTests/Info.plist index f7ff3086af6..1c1cf643694 100644 --- a/Tests/MapboxNavigationTests/Info.plist +++ b/Tests/MapboxNavigationTests/Info.plist @@ -15,12 +15,12 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.0 + 1.4.0 NSLocationAlwaysAndWhenInUseUsageDescription Location Usage Description NSLocationWhenInUseUsageDescription Location Usage Description CFBundleVersion - 44 + 45 diff --git a/Tests/TestHelper/Info.plist b/Tests/TestHelper/Info.plist index dd8f44a6288..b250746bafc 100644 --- a/Tests/TestHelper/Info.plist +++ b/Tests/TestHelper/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.3.0 + 1.4.0 CFBundleVersion - 44 + 45 diff --git a/custom-navigation.md b/custom-navigation.md index 09cf3d299c9..b561db323c3 100644 --- a/custom-navigation.md +++ b/custom-navigation.md @@ -30,7 +30,7 @@ To install Mapbox Core Navigation using [CocoaPods](https://cocoapods.org/): 1. Create a [Podfile](https://guides.cocoapods.org/syntax/podfile.html) with the following specification: ```ruby # Latest stable release - pod 'MapboxCoreNavigation', '~> 1.3' + pod 'MapboxCoreNavigation', '~> 1.4' # Latest prerelease pod 'MapboxCoreNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v1.3.0-beta.1' ``` @@ -57,7 +57,7 @@ To install Mapbox Navigation using [Carthage](https://github.com/Carthage/Cartha 1. Create a [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#github-repositories) with the following dependency: ```cartfile # Latest stable release - github "mapbox/mapbox-navigation-ios" ~> 1.3 + github "mapbox/mapbox-navigation-ios" ~> 1.4 # Latest prerelease github "mapbox/mapbox-navigation-ios" "v1.3.0-beta.1" ``` @@ -81,9 +81,9 @@ To install the MapboxCoreNavigation framework using [Swift Package Manager](http 1. Run `swift package init` to create a Package.swift, then add the following dependency: ```swift // Latest stable release - .package(name: "MapboxCoreNavigation", url: "https://github.com/mapbox/mapbox-navigation-ios.git", from: "1.3.0") + .package(name: "MapboxCoreNavigation", url: "https://github.com/mapbox/mapbox-navigation-ios.git", from: "1.4.0") // Latest prerelease - .package(name: "MapboxCoreNavigation", url: "https://github.com/mapbox/mapbox-navigation-ios.git", from: "1.3.0") + .package(name: "MapboxCoreNavigation", url: "https://github.com/mapbox/mapbox-navigation-ios.git", from: "1.4.0") ``` ### Using Xcode @@ -102,4 +102,4 @@ To install the MapboxCoreNavigation framework using [Swift Package Manager](http 1. Enter `https://github.com/mapbox/mapbox-navigation-ios.git` as the package repository and click Next. -1. Set Rules to Version, Up to Next Major, and enter `1.3.0` as the minimum version requirement. Click Next. +1. Set Rules to Version, Up to Next Major, and enter `1.4.0` as the minimum version requirement. Click Next. diff --git a/scripts/convert_string_files.sh b/scripts/convert_string_files.sh index 3553259aa78..8f078de53bd 100755 --- a/scripts/convert_string_files.sh +++ b/scripts/convert_string_files.sh @@ -3,7 +3,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" source "${DIR}/file_conversion.sh" -DIRECTORIES=( "${DIR}/../MapboxNavigation" "${DIR}/../MapboxCoreNavigation" "${DIR}/../Example" ) +DIRECTORIES=( "${DIR}/../Sources/MapboxNavigation" "${DIR}/../Sources/MapboxCoreNavigation" "${DIR}/../Example" ) for dir in ${DIRECTORIES[@]} do From fc0a06e1d99164d8b1fe036c2ceca8a21fb4119a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Fri, 9 Apr 2021 15:24:35 -0700 Subject: [PATCH 44/48] Update README.md --- README.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b372ccc4174..4a291bfbfde 100644 --- a/README.md +++ b/README.md @@ -47,11 +47,8 @@ To install Mapbox Navigation using [CocoaPods](https://cocoapods.org/): # Latest stable release pod 'MapboxNavigation', '~> 1.4' # Latest prerelease - pod 'MapboxCoreNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v1.3.0-beta.1' - pod 'MapboxNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v1.3.0-beta.1' - # Latest prerelease - pod 'MapboxCoreNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v2.0.0-beta.1' - pod 'MapboxNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v2.0.0-beta.1' + pod 'MapboxCoreNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v2.0.0-beta.3' + pod 'MapboxNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v2.0.0-beta.3' ``` 1. Run `pod repo update && pod install` and open the resulting Xcode workspace. @@ -77,8 +74,6 @@ To install Mapbox Navigation using [Carthage](https://github.com/Carthage/Cartha ```cartfile # Latest stable release github "mapbox/mapbox-navigation-ios" ~> 1.4 - # Latest prerelease - github "mapbox/mapbox-navigation-ios" "v1.3.0-beta.1" ``` 1. Run `./Carthage/Checkouts/mapbox-navigation-ios/scripts/wcarthage.sh bootstrap --platform iOS --cache-builds --use-netrc`. (wcarthage.sh is a temporary replacement for `carthage` to work around [a linker error in Xcode 12](https://github.com/Carthage/Carthage/issues/3019).) From 61932ae5cd81d3f718ab150c68c0af4148f091d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Thu, 15 Apr 2021 09:14:24 -0700 Subject: [PATCH 45/48] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4a291bfbfde..b10b195d7a1 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,8 @@ To install Mapbox Navigation using [CocoaPods](https://cocoapods.org/): # Latest stable release pod 'MapboxNavigation', '~> 1.4' # Latest prerelease - pod 'MapboxCoreNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v2.0.0-beta.3' - pod 'MapboxNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v2.0.0-beta.3' + pod 'MapboxCoreNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v2.0.0-beta.5' + pod 'MapboxNavigation', :git => 'https://github.com/mapbox/mapbox-navigation-ios.git', :tag => 'v2.0.0-beta.5' ``` 1. Run `pod repo update && pod install` and open the resulting Xcode workspace. From 41525d9487fbd26e6b3cb3817aa1112ec69e73d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Fri, 16 Apr 2021 09:53:19 -0700 Subject: [PATCH 46/48] Disabled codecov --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 47a35087e35..5ca4ba1069a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -302,7 +302,7 @@ workflows: name: "Xcode_11.4.1_iOS_12.2" xcode: "11.4.1" iOS: "12.2" - codecoverage: true + codecoverage: false - build-job: name: "Xcode_11.4.1_iOS_10.3.1" xcode: "11.4.1" From cf946f822afd06c0dc7fae6923eef787dd871465 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Sat, 8 May 2021 07:40:44 -0700 Subject: [PATCH 47/48] Update version of Quick/Quick and Quick/Nimble for Xcode 12.5 support. Remove unnecessary Cedar dependency. (#2978) --- Cartfile.private | 5 ++--- Cartfile.resolved | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Cartfile.private b/Cartfile.private index 5b1b85b10fe..fc226a56a3e 100644 --- a/Cartfile.private +++ b/Cartfile.private @@ -1,6 +1,5 @@ github "mapbox/MapboxGeocoder.swift" ~> 0.10 -github "Quick/Quick" ~> 2.0 -github "Quick/Nimble" ~> 8.0 -github "CedarBDD/Cedar" ~> 1.0 +github "Quick/Quick" ~> 3.1.2 +github "Quick/Nimble" ~> 9.0.1 github "linksmt/OHHTTPStubs" "563f48d3fab84ef04639649c770b00f4fa502cca" github "Udumft/SnappyShrimp" "xcode-11-4-build" diff --git a/Cartfile.resolved b/Cartfile.resolved index 769a35c63f1..f9f16d9dd3a 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -2,9 +2,8 @@ binary "https://api.mapbox.com/downloads/v2/carthage/mapbox-common/MapboxCommon- binary "https://api.mapbox.com/downloads/v2/carthage/mobile-maps/mapbox-ios-sdk-dynamic.json" "6.3.0" binary "https://api.mapbox.com/downloads/v2/carthage/mobile-navigation-native/MapboxNavigationNative.json" "32.0.0" binary "https://www.mapbox.com/ios-sdk/MapboxAccounts.json" "2.3.1" -github "CedarBDD/Cedar" "v1.0" -github "Quick/Nimble" "v8.1.2" -github "Quick/Quick" "v2.2.1" +github "Quick/Nimble" "v9.0.1" +github "Quick/Quick" "v3.1.2" github "Udumft/SnappyShrimp" "66f3e3ba70370ad5e889ac8ed72a8730ca79958e" github "Udumft/SwiftCLI" "da19d2a16cd5aa838d8fb7256e28c171bc67dd82" github "ceeK/Solar" "2.2.0" From 4b7364bb278b7343dc7149969852eadcf181e049 Mon Sep 17 00:00:00 2001 From: Maxim Makhun Date: Thu, 13 May 2021 16:51:21 +0300 Subject: [PATCH 48/48] Remove unused properties. --- Example/AppDelegate.swift | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Example/AppDelegate.swift b/Example/AppDelegate.swift index 62401328435..d07f984592a 100644 --- a/Example/AppDelegate.swift +++ b/Example/AppDelegate.swift @@ -13,15 +13,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { @available(iOS 12.0, *) lazy var carPlaySearchController: CarPlaySearchController = CarPlaySearchController() - @available(iOS 12.0, *) - lazy var interfaceController: CPInterfaceController? = nil - - @available(iOS 12.0, *) - lazy var carWindow: CPWindow? = nil - - @available(iOS 12.0, *) - lazy var sessionConfiguration: CPSessionConfiguration? = nil - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { if isRunningTests() { if window == nil {