diff --git a/CHANGELOG.md b/CHANGELOG.md index e99120c2..014cb1a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,13 @@ - Updated MapLibre Native dependency to ios-v6.0.0 (https://github.com/maplibre/maplibre-native/releases/tag/ios-v6.0.0). Implementers need to change the prefix MGL to MLN for all MapLibre Native classes that are referenced. - Only snap location to route if the location is within the `RouteControllerUserLocationSnappingDistance` - Add support for Swift Package Manager while dropping Carthage and Cocoapods. + - Removed implicit default dependencies on MapBox tileservers by requiring explicit styles URLs in more places. + - Merged in . + - BREAKING: Removed `MLNStyle` extensions referencing non-functioning MapBox styles, e.g. `MLNStyle.navigationGuidanceDayStyleURL`. + - Added `Day/NightStyle(styleURL:)` which takes an explicit URL to a hosting tileserver style. + - Added `Day/NightStyle(demoStyle: ())` as an explicit alternative when the user doesn't have a tileserver handy. This uses MapLibre's demo style and is intended for testing and demonstration use only. + - Deprecated `DayStyle()`/`NightStyle()` initializers because they were backed by an implicit tile service. If these default styles *are* still used, they'll now use the MapLibre demo style. + - `NavigationViewController` now expects explicit style URLs with `NavigationViewController(route:dayStyleURL:nightStyleURL:...)` or NavigationViewController(route:dayStyle:nightStyle:...)` and the existing initializer, which allowed "default" styles, is deprecated and uses the MapLibre demo styles. ## v2.0.0 (May 23, 2023) - Upgrade minimum iOS version from 11.0 to 12.0. diff --git a/MapboxNavigation/CarPlayMapViewController.swift b/MapboxNavigation/CarPlayMapViewController.swift index 79e4a6a7..4de78ece 100644 --- a/MapboxNavigation/CarPlayMapViewController.swift +++ b/MapboxNavigation/CarPlayMapViewController.swift @@ -50,8 +50,8 @@ class CarPlayMapViewController: UIViewController, MLNMapViewDelegate { super.viewDidLoad() self.styleManager = StyleManager(self) - self.styleManager.styles = [DayStyle(), NightStyle()] - + self.styleManager.styles = [DayStyle(demoStyle: ()), NightStyle(demoStyle: ())] + self.resetCamera(animated: false, altitude: CarPlayMapViewController.defaultAltitude) self.mapView.setUserTrackingMode(.followWithCourse, animated: true, completionHandler: nil) } diff --git a/MapboxNavigation/CarPlayNavigationViewController.swift b/MapboxNavigation/CarPlayNavigationViewController.swift index 7789e380..b7789fef 100644 --- a/MapboxNavigation/CarPlayNavigationViewController.swift +++ b/MapboxNavigation/CarPlayNavigationViewController.swift @@ -85,8 +85,8 @@ public class CarPlayNavigationViewController: UIViewController, MLNMapViewDelega view.addSubview(mapView) self.styleManager = StyleManager(self) - self.styleManager.styles = [DayStyle(), NightStyle()] - + self.styleManager.styles = [DayStyle(demoStyle: ()), NightStyle(demoStyle: ())] + self.resumeNotifications() self.routeController.resume() mapView.recenterMap() diff --git a/MapboxNavigation/DayStyle.swift b/MapboxNavigation/DayStyle.swift index 20c6acbf..d0f6dc55 100644 --- a/MapboxNavigation/DayStyle.swift +++ b/MapboxNavigation/DayStyle.swift @@ -45,9 +45,8 @@ private extension UIFont { */ @objc(MBDayStyle) open class DayStyle: Style { - public required init() { - super.init() - mapStyleURL = MLNStyle.navigationGuidanceDayStyleURL + @objc public required init(mapStyleURL: URL) { + super.init(mapStyleURL: mapStyleURL) styleType = .day statusBarStyle = .default } @@ -166,10 +165,8 @@ open class DayStyle: Style { */ @objc(MBNightStyle) open class NightStyle: DayStyle { - public required init() { - super.init() - mapStyleURL = MLNStyle.navigationGuidanceNightStyleURL - previewMapStyleURL = MLNStyle.navigationPreviewNightStyleURL + public required init(mapStyleURL: URL) { + super.init(mapStyleURL: mapStyleURL) styleType = .night statusBarStyle = .lightContent } diff --git a/MapboxNavigation/MLNStyle.swift b/MapboxNavigation/MLNStyle.swift deleted file mode 100644 index 2bc90056..00000000 --- a/MapboxNavigation/MLNStyle.swift +++ /dev/null @@ -1,74 +0,0 @@ -import Foundation -import MapLibre - -public extension MLNStyle { - // The Mapbox China Day Style URL. - internal static let mapboxChinaDayStyleURL = URL(string: "mapbox://styles/mapbox/streets-zh-v1")! - - // The Mapbox China Night Style URL. - internal static let mapboxChinaNightStyleURL = URL(string: "mapbox://styles/mapbox/dark-zh-v1")! - - /** - Returns the URL to the current version of the Mapbox Navigation Guidance Day style. - */ - @objc class var navigationGuidanceDayStyleURL: URL { - URL(string: "mapbox://styles/mapbox/navigation-guidance-day-v4")! - } - - /** - Returns the URL to the current version of the Mapbox Navigation Guidance Night style. - */ - @objc class var navigationGuidanceNightStyleURL: URL { - URL(string: "mapbox://styles/mapbox/navigation-guidance-night-v4")! - } - - /** - Returns the URL to the given version of the navigation guidance style. Available version are 1, 2, 3, and 4. - - We only have one version of navigation guidance style in China, so if you switch your endpoint to .cn, it will return the default day style. - */ - @objc class func navigationGuidanceDayStyleURL(version: Int) -> URL { - URL(string: "mapbox://styles/mapbox/navigation-guidance-day-v\(version)")! - } - - /** - Returns the URL to the given version of the navigation guidance style. Available version are 2, 3, and 4. - - We only have one version of navigation guidance style in China, so if you switch your endpoint to .cn, it will return the default night style. - */ - @objc class func navigationGuidanceNightStyleURL(version: Int) -> URL { - URL(string: "mapbox://styles/mapbox/navigation-guidance-night-v\(version)")! - } - - /** - Returns the URL to the current version of the Mapbox Navigation Preview Day style. - */ - @objc class var navigationPreviewDayStyleURL: URL { - URL(string: "mapbox://styles/mapbox/navigation-preview-day-v4")! - } - - /** - Returns the URL to the current version of the Mapbox Navigation Preview Night style. - */ - @objc class var navigationPreviewNightStyleURL: URL { - URL(string: "mapbox://styles/mapbox/navigation-preview-night-v4")! - } - - /** - Returns the URL to the given version of the Mapbox Navigation Preview Day style. Available versions are 1, 2, 3, and 4. - - We only have one version of Navigation Preview style in China, so if you switch your endpoint to .cn, it will return the default day style. - */ - @objc class func navigationPreviewDayStyleURL(version: Int) -> URL { - URL(string: "mapbox://styles/mapbox/navigation-guidance-day-v\(version)")! - } - - /** - Returns the URL to the given version of the Mapbox Navigation Preview Night style. Available versions are 2, 3, and 4. - - We only have one version of Navigation Preview style in China, so if you switch your endpoint to .cn, it will return the default night style. - */ - @objc class func navigationPreviewNightStyleURL(version: Int) -> URL { - URL(string: "mapbox://styles/mapbox/navigation-guidance-night-v\(version)")! - } -} diff --git a/MapboxNavigation/NavigationView.swift b/MapboxNavigation/NavigationView.swift index 007839f9..ca47ecc4 100644 --- a/MapboxNavigation/NavigationView.swift +++ b/MapboxNavigation/NavigationView.swift @@ -210,7 +210,7 @@ open class NavigationView: UIView { override open func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() - DayStyle().apply() + DayStyle(demoStyle: ()).apply() [self.mapView, self.instructionsBannerView, self.lanesView, self.bottomBannerView, self.nextBannerView].forEach { $0.prepareForInterfaceBuilder() } self.wayNameView.text = "Street Label" } diff --git a/MapboxNavigation/NavigationViewController.swift b/MapboxNavigation/NavigationViewController.swift index 7dc06535..805dd0c3 100644 --- a/MapboxNavigation/NavigationViewController.swift +++ b/MapboxNavigation/NavigationViewController.swift @@ -345,20 +345,97 @@ open class NavigationViewController: UIViewController { super.init(coder: aDecoder) } - /** - Initializes a `NavigationViewController` that provides turn by turn navigation for the given route. A optional `direction` object is needed for potential rerouting. - - See [Mapbox Directions](https://mapbox.github.io/mapbox-navigation-ios/directions/) for further information. - */ + /// Initializes a `NavigationViewController` that provides turn by turn navigation for the given route. + /// + /// ``` + /// let dayStyle = DayStyle(mapStyleURL: styleURL) + /// let vc = NavigationViewController(route: route, styles: [dayStyle]) + /// self.presentViewController(vc, animated: true) + /// ``` + /// - Parameters: + /// - route: The route to follow. + /// - directions: Used when recomputing a new route, for example if the user takes a wrong turn and needs re-routing. If unspecified, a default will be used. + /// - styles: The `[dayStyle]` or `[dayStyle, nightStyle]` styles used to render the map. If nil, the default styles will be used. + /// - routeController: Used to monitor the route and notify of changes to the route. If nil, a default will be used. + /// - locationManager: Tracks the users location along the route. If nil, a default will be used. + /// - voiceController: Produces voice instructions for route navigation. If nil, a default will be used. + /// + /// See [Mapbox Directions](https://mapbox.github.io/mapbox-navigation-ios/directions/) for further information. + @available(*, deprecated, message: "Use `init(for:dayStyle:...) or init(for:dayStyleURL:...)` instead.") @objc(initWithRoute:directions:styles:routeController:locationManager:voiceController:) + public convenience init(for route: Route, + directions: Directions = Directions.shared, + styles: [Style]? = [DayStyle(), NightStyle()], + routeController: RouteController? = nil, + locationManager: NavigationLocationManager? = nil, + voiceController: RouteVoiceController? = nil) { + let styles = styles ?? [] + assert(styles.count <= 2, "Having more than two styles is undefined.") + let dayStyle = styles.first ?? DayStyle(demoStyle: ()) + let nightStyle = styles.count > 1 ? styles[1] : NightStyle(mapStyleURL: dayStyle.mapStyleURL) + + self.init(for: route, dayStyle: dayStyle, nightStyle: nightStyle, directions: directions, routeController: routeController, locationManager: locationManager, voiceController: voiceController) + } + + /// Initializes a `NavigationViewController` that provides turn by turn navigation for the given route. + /// + /// - Parameters: + /// - route: The route to follow. + /// - dayStyleURL: URL for the style rules used to render the map during daylight hours. + /// - nightStyleURL: URL for the style rules used to render the map during nighttime hours. If nil, `dayStyleURL` will be used at night as well. + /// - directions: Used when recomputing a new route, for example if the user takes a wrong turn and needs re-routing. If unspecified, a default will be used. + /// - routeController: Used to monitor the route and notify of changes to the route. If nil, a default will be used. + /// - locationManager: Tracks the users location along the route. If nil, a default will be used. + /// - voiceController: Produces voice instructions for route navigation. If nil, a default will be used. + /// + /// See [Mapbox Directions](https://mapbox.github.io/mapbox-navigation-ios/directions/) for further information. + @objc(initWithRoute:dayStyleURL:nightStyleURL:directions:routeController:locationManager:voiceController:) + public convenience init(for route: Route, + dayStyleURL: URL, + nightStyleURL: URL? = nil, + directions: Directions = Directions.shared, + routeController: RouteController? = nil, + locationManager: NavigationLocationManager? = nil, + voiceController: RouteVoiceController? = nil) { + let dayStyle = DayStyle(mapStyleURL: dayStyleURL) + let nightStyle = NightStyle(mapStyleURL: nightStyleURL ?? dayStyleURL) + self.init(for: route, dayStyle: dayStyle, nightStyle: nightStyle, directions: directions, routeController: routeController, locationManager: locationManager, voiceController: voiceController) + } + + /// Initializes a `NavigationViewController` that provides turn by turn navigation for the given route. + /// + /// - Parameters: + /// - route: The route to follow. + /// - dayStyle: Style used to render the map during daylight hours. + /// - nightStyle: Style used to render the map during nighttime hours. If nil, `dayStyle` will be used at night as well. + /// - directions: Used when recomputing a new route, for example if the user takes a wrong turn and needs re-routing. If unspecified, a default will be used. + /// - routeController: Used to monitor the route and notify of changes to the route. If nil, a default will be used. + /// - locationManager: Tracks the users location along the route. If nil, a default will be used. + /// - voiceController: Produces voice instructions for route navigation. If nil, a default will be used. + /// + /// See [Mapbox Directions](https://mapbox.github.io/mapbox-navigation-ios/directions/) for further information. + @objc(initWithRoute:dayStyle:nightStyle:directions:routeController:locationManager:voiceController:) public required init(for route: Route, + dayStyle: Style, + nightStyle: Style? = nil, directions: Directions = Directions.shared, - styles: [Style]? = [DayStyle(), NightStyle()], routeController: RouteController? = nil, locationManager: NavigationLocationManager? = nil, voiceController: RouteVoiceController? = nil) { + let nightStyle = { + if let nightStyle { + return nightStyle + } + + let dayCopy: Style = dayStyle.copy() as! Style + dayCopy.styleType = .night + return dayCopy + }() + + assert(dayStyle.styleType == .day) + assert(nightStyle.styleType == .night) + super.init(nibName: nil, bundle: nil) - self.locationManager = locationManager ?? NavigationLocationManager() let routeController = routeController ?? RouteController(along: route, directions: directions, locationManager: self.locationManager) self.routeController = routeController @@ -387,8 +464,8 @@ open class NavigationViewController: UIViewController { mapViewController.reportButton.isHidden = !self.showsReportFeedback self.styleManager = StyleManager(self) - self.styleManager.styles = styles ?? [DayStyle(), NightStyle()] - + self.styleManager.styles = [dayStyle, nightStyle] + if !(route.routeOptions is NavigationRouteOptions) { print("`Route` was created using `RouteOptions` and not `NavigationRouteOptions`. Although not required, this may lead to a suboptimal navigation experience. Without `NavigationRouteOptions`, it is not guaranteed you will get congestion along the route line, better ETAs and ETA label color dependent on congestion.") } @@ -515,8 +592,8 @@ open class NavigationViewController: UIViewController { let locationManager = routeController.locationManager.copy() as! NavigationLocationManager let directions = routeController.directions let route = routeController.routeProgress.route - let navigationViewController = NavigationViewController(for: route, directions: directions, routeController: routeController, locationManager: locationManager) - + let navigationViewController = NavigationViewController(for: route, dayStyle: DayStyle(demoStyle: ()), directions: directions, routeController: routeController, locationManager: locationManager) + window.rootViewController?.topMostViewController()?.present(navigationViewController, animated: true, completion: { navigationViewController.isUsedInConjunctionWithCarPlayWindow = true }) diff --git a/MapboxNavigation/Style.swift b/MapboxNavigation/Style.swift index 5d816a23..4ac532e6 100644 --- a/MapboxNavigation/Style.swift +++ b/MapboxNavigation/Style.swift @@ -34,28 +34,52 @@ open class Style: NSObject { /** URL of the style to display on the map during turn-by-turn navigation. */ - @objc open var mapStyleURL: URL = MLNStyle.navigationGuidanceDayStyleURL - + @objc open var mapStyleURL: URL = MLNStyle.defaultStyle().url + #if canImport(CarPlay) /** URL of the style to display on the map when previewing a route, for example on CarPlay. */ - @objc open var previewMapStyleURL = MLNStyle.navigationPreviewDayStyleURL + @objc open var previewMapStyleURL = MLNStyle.defaultStyle().url #else /** URL of the style to display on the map when previewing a route. This property is currently unused by default, but you can use it to present your own route preview map. */ - @objc open var previewMapStyleURL = MLNStyle.navigationPreviewDayStyleURL + @objc open var previewMapStyleURL = MLNStyle.defaultStyle().url #endif /** Applies the style for all changed properties. */ @objc open func apply() {} - - @objc override public required init() {} + + @available(*, deprecated, message: "Use `init(mapStyleURL:)` to specify your map style. If you want to try the demo maplibre tiles, use init(demoStyle: ()).") + @objc override public convenience init() { + self.init(demoStyle: ()) + } + + @objc public required init(mapStyleURL: URL) { + self.mapStyleURL = mapStyleURL + } + + @objc public convenience init(demoStyle: ()) { + self.init(mapStyleURL: MLNStyle.defaultStyle().url) + } +} + +extension Style: NSCopying { + public func copy(with zone: NSZone? = nil) -> Any { + let copy = Self(mapStyleURL: self.mapStyleURL) + copy.tintColor = self.tintColor + copy.statusBarStyle = self.statusBarStyle + copy.fontFamily = self.fontFamily + copy.styleType = self.styleType + copy.mapStyleURL = self.mapStyleURL + copy.previewMapStyleURL = self.previewMapStyleURL + return copy + } } /** diff --git a/MapboxNavigationTests/Sources/Tests/NavigationViewControllerTests.swift b/MapboxNavigationTests/Sources/Tests/NavigationViewControllerTests.swift index 5253487c..a9d1e9ec 100644 --- a/MapboxNavigationTests/Sources/Tests/NavigationViewControllerTests.swift +++ b/MapboxNavigationTests/Sources/Tests/NavigationViewControllerTests.swift @@ -17,6 +17,7 @@ class NavigationViewControllerTests: XCTestCase { lazy var dependencies: (navigationViewController: NavigationViewController, startLocation: CLLocation, poi: [CLLocation], endLocation: CLLocation, voice: RouteVoiceController) = { let voice = FakeVoiceController() let nav = NavigationViewController(for: initialRoute, + dayStyle: DayStyle(demoStyle: ()), directions: Directions(accessToken: "garbage", host: nil), voiceController: voice) @@ -88,7 +89,10 @@ class NavigationViewControllerTests: XCTestCase { } func testNavigationShouldNotCallStyleManagerDidRefreshAppearanceMoreThanOnceWithOneStyle() { - let navigationViewController = NavigationViewController(for: initialRoute, directions: fakeDirections, styles: [DayStyle()], voiceController: FakeVoiceController()) + let navigationViewController = NavigationViewController(for: initialRoute, + dayStyle: DayStyle(demoStyle: ()), + directions: fakeDirections, + voiceController: FakeVoiceController()) let routeController = navigationViewController.routeController! navigationViewController.styleManager.delegate = self @@ -104,7 +108,7 @@ class NavigationViewControllerTests: XCTestCase { // If tunnel flags are enabled and we need to switch styles, we should not force refresh the map style because we have only 1 style. func testNavigationShouldNotCallStyleManagerDidRefreshAppearanceWhenOnlyOneStyle() { - let navigationViewController = NavigationViewController(for: initialRoute, directions: fakeDirections, styles: [NightStyle()], voiceController: FakeVoiceController()) + let navigationViewController = NavigationViewController(for: initialRoute, dayStyle: DayStyle(demoStyle: ()), directions: fakeDirections, voiceController: FakeVoiceController()) let routeController = navigationViewController.routeController! navigationViewController.styleManager.delegate = self @@ -119,7 +123,7 @@ class NavigationViewControllerTests: XCTestCase { } func testNavigationShouldNotCallStyleManagerDidRefreshAppearanceMoreThanOnceWithTwoStyles() { - let navigationViewController = NavigationViewController(for: initialRoute, directions: fakeDirections, styles: [DayStyle(), NightStyle()], voiceController: FakeVoiceController()) + let navigationViewController = NavigationViewController(for: initialRoute, dayStyle: DayStyle(demoStyle: ()), nightStyle: NightStyle(demoStyle: ()), directions: fakeDirections, voiceController: FakeVoiceController()) let routeController = navigationViewController.routeController! navigationViewController.styleManager.delegate = self @@ -173,8 +177,8 @@ class NavigationViewControllerTests: XCTestCase { func testDestinationAnnotationUpdatesUponReroute() { let styleLoaded = XCTestExpectation(description: "Style Loaded") - let navigationViewController = NavigationViewControllerTestable(for: initialRoute, styles: [TestableDayStyle()], styleLoaded: styleLoaded) - + let navigationViewController = NavigationViewControllerTestable(for: initialRoute, dayStyle: DayStyle.blankStyleForTesting, styleLoaded: styleLoaded) + // wait for the style to load -- routes won't show without it. wait(for: [styleLoaded], timeout: 5) navigationViewController.route = self.initialRoute @@ -248,21 +252,19 @@ private extension NavigationViewControllerTests { class NavigationViewControllerTestable: NavigationViewController { var styleLoadedExpectation: XCTestExpectation - + required init(for route: Route, - directions: Directions = Directions(accessToken: "abc", host: ""), - styles: [Style]? = [DayStyle(), NightStyle()], - locationManager: NavigationLocationManager? = NavigationLocationManager(), + dayStyle: Style, styleLoaded: XCTestExpectation) { self.styleLoadedExpectation = styleLoaded - super.init(for: route, directions: directions, styles: styles, locationManager: locationManager, voiceController: FakeVoiceController()) + super.init(for: route, dayStyle: dayStyle, directions: Directions(accessToken: "abc", host: ""), voiceController: FakeVoiceController()) } - @objc(initWithRoute:directions:styles:routeController:locationManager:voiceController:) - required init(for route: Route, directions: Directions, styles: [Style]?, routeController: RouteController?, locationManager: NavigationLocationManager?, voiceController: RouteVoiceController?) { - fatalError("init(for:directions:styles:routeController:locationManager:voiceController:) is not supported in this testing subclass.") + @objc(initWithRoute:dayStyle:nightStyle:directions:routeController:locationManager:voiceController:) + required init(for route: Route, dayStyle: Style, nightStyle: Style? = nil, directions: Directions = Directions.shared, routeController: RouteController? = nil, locationManager: NavigationLocationManager? = nil, voiceController: RouteVoiceController? = nil) { + fatalError("init(for:directions:dayStyle:nightStyle:routeController:locationManager:voiceController:) has not been implemented") } - + func mapView(_ mapView: MLNMapView, didFinishLoading style: MLNStyle) { self.styleLoadedExpectation.fulfill() } @@ -273,10 +275,9 @@ class NavigationViewControllerTestable: NavigationViewController { } } -class TestableDayStyle: DayStyle { - required init() { - super.init() - mapStyleURL = Fixture.blankStyle +extension DayStyle { + static var blankStyleForTesting: Self { + Self(mapStyleURL: Fixture.blankStyle) } }