diff --git a/MapboxNavigation.xcodeproj/project.pbxproj b/MapboxNavigation.xcodeproj/project.pbxproj index 5eb85ac1694..73e87048ae0 100644 --- a/MapboxNavigation.xcodeproj/project.pbxproj +++ b/MapboxNavigation.xcodeproj/project.pbxproj @@ -188,6 +188,8 @@ 8AAE94A126A60ADE00AA1127 /* CarPlayMapViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AAE94A026A60ADE00AA1127 /* CarPlayMapViewControllerDelegate.swift */; }; 8AB316C926BCA56D00C3AC76 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB316C826BCA56D00C3AC76 /* UIViewController.swift */; }; 8AB316CB26BCA72300C3AC76 /* CGSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB316CA26BCA72300C3AC76 /* CGSize.swift */; }; + 8AB316A926BA026B00C3AC76 /* MapTemplateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB316A826BA026B00C3AC76 /* MapTemplateProvider.swift */; }; + 8AB316AB26BA029100C3AC76 /* MapTemplateProviderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB316AA26BA029100C3AC76 /* MapTemplateProviderDelegate.swift */; }; 8ABB9E75268E0140009013A5 /* NavigationCameraTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABB9E74268E0140009013A5 /* NavigationCameraTests.swift */; }; 8ABCD6A426AA0D9400B121B9 /* route-for-navigation-camera-bearing-smoothing.json in Resources */ = {isa = PBXBuildFile; fileRef = 8ABCD6A326AA0D9400B121B9 /* route-for-navigation-camera-bearing-smoothing.json */; }; 8AC3965325DC66570027A035 /* NavigationCameraType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AC3965225DC66570027A035 /* NavigationCameraType.swift */; }; @@ -668,6 +670,8 @@ 8AB316AE26BB315100C3AC76 /* CPMapTemplate+MBTestable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CPMapTemplate+MBTestable.m"; sourceTree = ""; }; 8AB316C826BCA56D00C3AC76 /* UIViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = ""; }; 8AB316CA26BCA72300C3AC76 /* CGSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGSize.swift; sourceTree = ""; }; + 8AB316A826BA026B00C3AC76 /* MapTemplateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTemplateProvider.swift; sourceTree = ""; }; + 8AB316AA26BA029100C3AC76 /* MapTemplateProviderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTemplateProviderDelegate.swift; sourceTree = ""; }; 8ABB9E74268E0140009013A5 /* NavigationCameraTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationCameraTests.swift; sourceTree = ""; }; 8ABCD6A326AA0D9400B121B9 /* route-for-navigation-camera-bearing-smoothing.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "route-for-navigation-camera-bearing-smoothing.json"; sourceTree = ""; }; 8AC3965225DC66570027A035 /* NavigationCameraType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationCameraType.swift; sourceTree = ""; }; @@ -1069,6 +1073,8 @@ 8AAE94A026A60ADE00AA1127 /* CarPlayMapViewControllerDelegate.swift */, C5FFAC1420D96F5B009E7F98 /* CarPlayNavigationViewController.swift */, 8AEB28AB265FF42500EC7892 /* CarPlayNavigationViewControllerDelegate.swift */, + 8AB316A826BA026B00C3AC76 /* MapTemplateProvider.swift */, + 8AB316AA26BA029100C3AC76 /* MapTemplateProviderDelegate.swift */, 358E31D522562697009B3EC2 /* CarPlayCompassView.swift */, 352C35BF2134958F00D77796 /* RecentItem.swift */, 8A1943A82685DC680066E2F8 /* NavigationGeocodedPlacemark.swift */, @@ -2389,7 +2395,9 @@ 351BEC051E5BCC6C006FE110 /* LaneView.swift in Sources */, C5A7EC5C1FD610A80008B9BA /* VisualInstructionComponent.swift in Sources */, CFD47D9020FD85EC00BC1E49 /* ResourceOptionsManager.swift in Sources */, + 8AB316AB26BA029100C3AC76 /* MapTemplateProviderDelegate.swift in Sources */, B44177F82649B08400781319 /* UserLocationStyle.swift in Sources */, + 8AB316A926BA026B00C3AC76 /* MapTemplateProvider.swift in Sources */, 351BEC0D1E5BCC72006FE110 /* Bundle.swift in Sources */, 8DF399B21FB257B30034904C /* UIGestureRecognizer.swift in Sources */, DA8F3A7823B5DB7900B56786 /* SpeedLimitStyleKit.swift in Sources */, diff --git a/Sources/MapboxNavigation/CarPlayManager.swift b/Sources/MapboxNavigation/CarPlayManager.swift index bfe0a04c9de..0fdafbac743 100644 --- a/Sources/MapboxNavigation/CarPlayManager.swift +++ b/Sources/MapboxNavigation/CarPlayManager.swift @@ -79,9 +79,7 @@ public class CarPlayManager: NSObject { */ public var styles: [Style] { didSet { - if let mapViewController = carPlayMapViewController { - mapViewController.styles = styles - } + carPlayMapViewController?.styles = styles carPlayNavigationViewController?.styles = styles } } @@ -182,7 +180,7 @@ public class CarPlayManager: NSObject { private weak var navigationService: NavigationService? private var idleTimerCancellable: IdleTimerManager.Cancellable? - internal var mapTemplateProvider: MapTemplateProvider + var mapTemplateProvider: MapTemplateProvider /** Initializes a new CarPlay manager that manages a connection to the CarPlay interface. @@ -200,10 +198,10 @@ public class CarPlayManager: NSObject { carPlayNavigationViewControllerClass: nil) } - internal init(styles: [Style]? = nil, - directions: Directions? = nil, - eventsManager: NavigationEventsManager? = nil, - carPlayNavigationViewControllerClass: CarPlayNavigationViewController.Type? = nil) { + init(styles: [Style]? = nil, + directions: Directions? = nil, + eventsManager: NavigationEventsManager? = nil, + carPlayNavigationViewControllerClass: CarPlayNavigationViewController.Type? = nil) { self.styles = styles ?? [DayStyle(), NightStyle()] let mapboxDirections = directions ?? NavigationSettings.shared.directions self.directions = mapboxDirections @@ -331,11 +329,11 @@ extension CarPlayManager: CPApplicationDelegate { } func reloadButtons(for mapTemplate: CPMapTemplate) { - guard let mapViewController = carPlayMapViewController else { + guard let carPlayMapViewController = carPlayMapViewController else { return } - let traitCollection = mapViewController.traitCollection + let traitCollection = carPlayMapViewController.traitCollection if let leadingButtons = delegate?.carPlayManager(self, leadingNavigationBarButtonsCompatibleWith: traitCollection, @@ -356,7 +354,7 @@ extension CarPlayManager: CPApplicationDelegate { in: mapTemplate, for: .browsing) { mapTemplate.mapButtons = mapButtons - } else if let mapButtons = self.browsingMapButtons(for: mapTemplate) { + } else if let mapButtons = browsingMapButtons(for: mapTemplate) { mapTemplate.mapButtons = mapButtons } } @@ -369,7 +367,7 @@ extension CarPlayManager: CPApplicationDelegate { in: mapTemplate, for: .browsing) { mapTemplate.mapButtons = mapButtons - } else if let mapButtons = self.browsingMapButtons(for: mapTemplate) { + } else if let mapButtons = browsingMapButtons(for: mapTemplate) { mapTemplate.mapButtons = mapButtons } @@ -378,16 +376,17 @@ extension CarPlayManager: CPApplicationDelegate { } private func browsingMapButtons(for mapTemplate: CPMapTemplate) -> [CPMapButton]? { - guard let mapViewController = carPlayMapViewController else { + guard let carPlayMapViewController = carPlayMapViewController else { return nil } var mapButtons = [ - mapViewController.recenterButton, - mapViewController.zoomInButton, - mapViewController.zoomOutButton + carPlayMapViewController.recenterButton, + carPlayMapViewController.zoomInButton, + carPlayMapViewController.zoomOutButton ] - let panMapButton = mapViewController.panMapButton ?? mapViewController.panningInterfaceDisplayButton(for: mapTemplate) - mapViewController.panMapButton = panMapButton + let panMapButton = carPlayMapViewController.panMapButton ?? + carPlayMapViewController.panningInterfaceDisplayButton(for: mapTemplate) + carPlayMapViewController.panMapButton = panMapButton mapButtons.insert(panMapButton, at: 1) return mapButtons @@ -400,13 +399,17 @@ extension CarPlayManager: CPApplicationDelegate { extension CarPlayManager: CPInterfaceControllerDelegate { public func templateWillAppear(_ template: CPTemplate, animated: Bool) { + delegate?.carPlayManager(self, templateWillAppear: template, animated: animated) + if template == interfaceController?.rootTemplate, - let mapViewController = carPlayMapViewController { - mapViewController.recenterButton.isHidden = true + let carPlayMapViewController = carPlayMapViewController { + carPlayMapViewController.recenterButton.isHidden = true } } public func templateDidAppear(_ template: CPTemplate, animated: Bool) { + delegate?.carPlayManager(self, templateDidAppear: template, animated: animated) + guard interfaceController?.topTemplate == mainMapTemplate, template == interfaceController?.rootTemplate, let carPlayMapViewController = carPlayMapViewController else { return } @@ -417,6 +420,8 @@ extension CarPlayManager: CPInterfaceControllerDelegate { } public func templateWillDisappear(_ template: CPTemplate, animated: Bool) { + delegate?.carPlayManager(self, templateWillDisappear: template, animated: animated) + guard let interfaceController = interfaceController, let topTemplate = interfaceController.topTemplate, type(of: topTemplate) == CPSearchTemplate.self || @@ -424,14 +429,26 @@ extension CarPlayManager: CPInterfaceControllerDelegate { navigationMapView?.navigationCamera.follow() } + + public func templateDidDisappear(_ template: CPTemplate, animated: Bool) { + delegate?.carPlayManager(self, templateDidDisappear: template, animated: animated) + } } @available(iOS 12.0, *) extension CarPlayManager { + /** + Calculates routes to the given destination using the [Mapbox Directions API](https://www.mapbox.com/api-documentation/navigation/#directions) and previews them on a map. + + Upon successful calculation a new template will be pushed onto the template navigation hierarchy. + + - parameter destination: A final destination `Waypoint`. + - parameter completionHandler: A closure to be executed when the calculation completes. + */ public func previewRoutes(to destination: Waypoint, completionHandler: @escaping CompletionHandler) { - guard let rootViewController = carPlayMapViewController, - let userLocation = rootViewController.navigationMapView.mapView.location.latestLocation else { + guard let carPlayMapViewController = carPlayMapViewController, + let userLocation = carPlayMapViewController.navigationMapView.mapView.location.latestLocation else { completionHandler() return } @@ -451,29 +468,46 @@ extension CarPlayManager { previewRoutes(between: [origin, destination], completionHandler: completionHandler) } + /** + Allows to preview routes for a list of `Waypoint` objects. + + - parameter waypoints: A list of `Waypoint` objects. + - parameter completionHandler: A closure to be executed when the calculation completes. + */ public func previewRoutes(between waypoints: [Waypoint], completionHandler: @escaping CompletionHandler) { let options = NavigationRouteOptions(waypoints: waypoints) previewRoutes(for: options, completionHandler: completionHandler) } + /** + Calculates routes satisfying the given options using the [Mapbox Directions API](https://www.mapbox.com/api-documentation/navigation/#directions) and previews them on a map. + + - parameter routeOptions: A `RouteOptions` object, which specifies the criteria for results + returned by the Mapbox Directions API. + - parameter completionHandler: A closure to be executed when the calculation completes. + */ public func previewRoutes(for options: RouteOptions, completionHandler: @escaping CompletionHandler) { calculate(options) { [weak self] (session, result) in + guard let self = self else { + completionHandler() + return + } - self?.didCalculate(result, - in: session, - for: options, - completionHandler: completionHandler) + self.didCalculate(result, + in: session, + for: options, + completionHandler: completionHandler) } } - internal func calculate(_ options: RouteOptions, completionHandler: @escaping Directions.RouteCompletionHandler) { + func calculate(_ options: RouteOptions, completionHandler: @escaping Directions.RouteCompletionHandler) { directions.calculateWithCache(options: options, completionHandler: completionHandler) } - internal func didCalculate(_ result: Result, - in session: Directions.Session, - for routeOptions: RouteOptions, - completionHandler: CompletionHandler) { + func didCalculate(_ result: Result, + in session: Directions.Session, + for routeOptions: RouteOptions, + completionHandler: CompletionHandler) { defer { completionHandler() } @@ -826,6 +860,8 @@ extension CarPlayManager: CarPlayMapViewControllerDelegate { } } +// MARK: - MapTemplateProviderDelegate methods + @available(iOS 12.0, *) extension CarPlayManager: MapTemplateProviderDelegate { @@ -895,50 +931,3 @@ extension CarPlayManager { idleTimerCancellable = nil } } - -@available(iOS 12.0, *) -internal protocol MapTemplateProviderDelegate: AnyObject { - - func mapTemplateProvider(_ provider: MapTemplateProvider, - mapTemplate: CPMapTemplate, - leadingNavigationBarButtonsCompatibleWith traitCollection: UITraitCollection, - for activity: CarPlayActivity) -> [CPBarButton]? - - func mapTemplateProvider(_ provider: MapTemplateProvider, - mapTemplate: CPMapTemplate, - trailingNavigationBarButtonsCompatibleWith traitCollection: UITraitCollection, - for activity: CarPlayActivity) -> [CPBarButton]? -} - -@available(iOS 12.0, *) -internal class MapTemplateProvider: NSObject { - - weak var delegate: MapTemplateProviderDelegate? - - func mapTemplate(forPreviewing trip: CPTrip, - traitCollection: UITraitCollection, - mapDelegate: CPMapTemplateDelegate) -> CPMapTemplate { - let mapTemplate = createMapTemplate() - mapTemplate.mapDelegate = mapDelegate - - if let leadingButtons = delegate?.mapTemplateProvider(self, - mapTemplate: mapTemplate, - leadingNavigationBarButtonsCompatibleWith: traitCollection, - for: .previewing) { - mapTemplate.leadingNavigationBarButtons = leadingButtons - } - - if let trailingButtons = delegate?.mapTemplateProvider(self, - mapTemplate: mapTemplate, - trailingNavigationBarButtonsCompatibleWith: traitCollection, - for: .previewing) { - mapTemplate.trailingNavigationBarButtons = trailingButtons - } - - return mapTemplate - } - - open func createMapTemplate() -> CPMapTemplate { - return CPMapTemplate() - } -} diff --git a/Sources/MapboxNavigation/CarPlayManagerDelegate.swift b/Sources/MapboxNavigation/CarPlayManagerDelegate.swift index 01b28fe091f..cf7249be2d9 100644 --- a/Sources/MapboxNavigation/CarPlayManagerDelegate.swift +++ b/Sources/MapboxNavigation/CarPlayManagerDelegate.swift @@ -161,6 +161,34 @@ public protocol CarPlayManagerDelegate: AnyObject, UnimplementedLogging { didAdd finalDestinationAnnotation: PointAnnotation, to parentViewController: UIViewController, pointAnnotationManager: PointAnnotationManager) + + /** + Called when a template presented by the `CarPlayManager` is about to appear on the screen. + */ + func carPlayManager(_ carPlayManager: CarPlayManager, + templateWillAppear template: CPTemplate, + animated: Bool) + + /** + Called when a template presented by the `CarPlayManager` has finished appearing on the screen. + */ + func carPlayManager(_ carPlayManager: CarPlayManager, + templateDidAppear template: CPTemplate, + animated: Bool) + + /** + Called when a template presented by the `CarPlayManager` is about to disappear from the screen. + */ + func carPlayManager(_ carPlayManager: CarPlayManager, + templateWillDisappear template: CPTemplate, + animated: Bool) + + /** + Called when a template presented by the `CarPlayManager` has finished disappearing from the screen. + */ + func carPlayManager(_ carPlayManager: CarPlayManager, + templateDidDisappear template: CPTemplate, + animated: Bool) } @available(iOS 12.0, *) @@ -273,4 +301,40 @@ public extension CarPlayManagerDelegate { pointAnnotationManager: PointAnnotationManager) { logUnimplemented(protocolType: CarPlayManagerDelegate.self, level: .debug) } + + /** + `UnimplementedLogging` prints a warning to standard output the first time this method is called. + */ + func carPlayManager(_ carPlayManager: CarPlayManager, + templateWillAppear template: CPTemplate, + animated: Bool) { + logUnimplemented(protocolType: CarPlayManagerDelegate.self, level: .debug) + } + + /** + `UnimplementedLogging` prints a warning to standard output the first time this method is called. + */ + func carPlayManager(_ carPlayManager: CarPlayManager, + templateDidAppear template: CPTemplate, + animated: Bool) { + logUnimplemented(protocolType: CarPlayManagerDelegate.self, level: .debug) + } + + /** + `UnimplementedLogging` prints a warning to standard output the first time this method is called. + */ + func carPlayManager(_ carPlayManager: CarPlayManager, + templateWillDisappear template: CPTemplate, + animated: Bool) { + logUnimplemented(protocolType: CarPlayManagerDelegate.self, level: .debug) + } + + /** + `UnimplementedLogging` prints a warning to standard output the first time this method is called. + */ + func carPlayManager(_ carPlayManager: CarPlayManager, + templateDidDisappear template: CPTemplate, + animated: Bool) { + logUnimplemented(protocolType: CarPlayManagerDelegate.self, level: .debug) + } } diff --git a/Sources/MapboxNavigation/CarPlayMapViewController.swift b/Sources/MapboxNavigation/CarPlayMapViewController.swift index 929a594332a..893beb60036 100644 --- a/Sources/MapboxNavigation/CarPlayMapViewController.swift +++ b/Sources/MapboxNavigation/CarPlayMapViewController.swift @@ -113,12 +113,12 @@ public class CarPlayMapViewController: UIViewController { /** The map button property for hiding or showing the pan map button. */ - internal(set) public var panMapButton: CPMapButton? + public internal(set) var panMapButton: CPMapButton? /** The map button property for exiting the pan map mode. */ - internal(set) public var dismissPanningButton: CPMapButton? + public internal(set) var dismissPanningButton: CPMapButton? // MARK: - Initialization methods @@ -306,10 +306,9 @@ extension CarPlayMapViewController: StyleManagerDelegate { } public func styleManager(_ styleManager: StyleManager, didApply style: Style) { - let styleURL = style.previewMapStyleURL let mapboxMapStyle = navigationMapView.mapView.mapboxMap.style if mapboxMapStyle.uri?.rawValue != style.mapStyleURL.absoluteString { - mapboxMapStyle.uri = StyleURI(url: styleURL) + mapboxMapStyle.uri = StyleURI(url: style.previewMapStyleURL) } } diff --git a/Sources/MapboxNavigation/CarPlayNavigationViewController.swift b/Sources/MapboxNavigation/CarPlayNavigationViewController.swift index 52b1af3faf3..8e5b6ca0fc6 100644 --- a/Sources/MapboxNavigation/CarPlayNavigationViewController.swift +++ b/Sources/MapboxNavigation/CarPlayNavigationViewController.swift @@ -633,8 +633,9 @@ extension CarPlayNavigationViewController: StyleManagerDelegate { } public func styleManager(_ styleManager: StyleManager, didApply style: Style) { - if navigationMapView?.mapView.mapboxMap.style.uri?.rawValue != style.mapStyleURL.absoluteString { - navigationMapView?.mapView.mapboxMap.style.uri = StyleURI(url: style.mapStyleURL) + let mapboxMapStyle = navigationMapView?.mapView.mapboxMap.style + if mapboxMapStyle?.uri?.rawValue != style.mapStyleURL.absoluteString { + mapboxMapStyle?.uri = StyleURI(url: style.mapStyleURL) } } diff --git a/Sources/MapboxNavigation/MapTemplateProvider.swift b/Sources/MapboxNavigation/MapTemplateProvider.swift new file mode 100644 index 00000000000..6312ca99c80 --- /dev/null +++ b/Sources/MapboxNavigation/MapTemplateProvider.swift @@ -0,0 +1,34 @@ +import CarPlay + +@available(iOS 12.0, *) +class MapTemplateProvider: NSObject { + + weak var delegate: MapTemplateProviderDelegate? + + func mapTemplate(forPreviewing trip: CPTrip, + traitCollection: UITraitCollection, + mapDelegate: CPMapTemplateDelegate) -> CPMapTemplate { + let mapTemplate = createMapTemplate() + mapTemplate.mapDelegate = mapDelegate + + if let leadingButtons = delegate?.mapTemplateProvider(self, + mapTemplate: mapTemplate, + leadingNavigationBarButtonsCompatibleWith: traitCollection, + for: .previewing) { + mapTemplate.leadingNavigationBarButtons = leadingButtons + } + + if let trailingButtons = delegate?.mapTemplateProvider(self, + mapTemplate: mapTemplate, + trailingNavigationBarButtonsCompatibleWith: traitCollection, + for: .previewing) { + mapTemplate.trailingNavigationBarButtons = trailingButtons + } + + return mapTemplate + } + + func createMapTemplate() -> CPMapTemplate { + return CPMapTemplate() + } +} diff --git a/Sources/MapboxNavigation/MapTemplateProviderDelegate.swift b/Sources/MapboxNavigation/MapTemplateProviderDelegate.swift new file mode 100644 index 00000000000..ef8d6bf2c6d --- /dev/null +++ b/Sources/MapboxNavigation/MapTemplateProviderDelegate.swift @@ -0,0 +1,15 @@ +import CarPlay + +@available(iOS 12.0, *) +protocol MapTemplateProviderDelegate: AnyObject { + + func mapTemplateProvider(_ provider: MapTemplateProvider, + mapTemplate: CPMapTemplate, + leadingNavigationBarButtonsCompatibleWith traitCollection: UITraitCollection, + for activity: CarPlayActivity) -> [CPBarButton]? + + func mapTemplateProvider(_ provider: MapTemplateProvider, + mapTemplate: CPMapTemplate, + trailingNavigationBarButtonsCompatibleWith traitCollection: UITraitCollection, + for activity: CarPlayActivity) -> [CPBarButton]? +}