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 36722659..87066d09 100644 --- a/MapboxNavigation/DayStyle.swift +++ b/MapboxNavigation/DayStyle.swift @@ -45,13 +45,12 @@ private extension UIFont { */ @objc(MBDayStyle) open class DayStyle: Style { - public required init() { - super.init() - mapStyleURL = MLNStyle.defaultStyle().url + @objc public required init(mapStyleURL: URL) { + super.init(mapStyleURL: mapStyleURL) styleType = .day statusBarStyle = .default } - + override open func apply() { super.apply() @@ -166,10 +165,8 @@ open class DayStyle: Style { */ @objc(MBNightStyle) open class NightStyle: DayStyle { - public required init() { - super.init() - mapStyleURL = MLNStyle.defaultStyle().url - previewMapStyleURL = MLNStyle.defaultStyle().url + public required init(mapStyleURL: URL) { + super.init(mapStyleURL: mapStyleURL) styleType = .night statusBarStyle = .lightContent } 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 38bafc66..76206f47 100644 --- a/MapboxNavigation/NavigationViewController.swift +++ b/MapboxNavigation/NavigationViewController.swift @@ -345,32 +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 `directions` object is needed for potential rerouting. + /// Initializes a `NavigationViewController` that provides turn by turn navigation for the given route. /// /// ``` - /// var dayStyle = DayStyle() - /// dayStyle.mapStyleURL = styleURL + /// 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. + /// - 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:directions:dayStyle:...) or init(for:directions: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: DayStyle = dayStyle.copy() as! DayStyle + 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 @@ -399,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.") } @@ -527,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 5b4e7d78..134748c7 100644 --- a/MapboxNavigation/Style.swift +++ b/MapboxNavigation/Style.swift @@ -54,8 +54,19 @@ open class Style: NSObject { 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) + } } /** diff --git a/MapboxNavigationTests/Sources/Tests/NavigationViewControllerTests.swift b/MapboxNavigationTests/Sources/Tests/NavigationViewControllerTests.swift index 5253487c..273c9e90 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,9 @@ 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()) + // REVIEW: is this right? Does it make sense that there would ever be *only* a night style? + // 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 +125,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 +179,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 +254,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 +277,9 @@ class NavigationViewControllerTestable: NavigationViewController { } } -class TestableDayStyle: DayStyle { - required init() { - super.init() - mapStyleURL = Fixture.blankStyle +extension DayStyle { + static var blankStyleForTesting: Self { + Self(mapStyleURL: Fixture.blankStyle) } }