Skip to content

Commit

Permalink
Merge pull request #1836 from mapbox/1ec5-carplay-custom-style-1823
Browse files Browse the repository at this point in the history
Allow style customization in CarPlay
  • Loading branch information
1ec5 authored Nov 15, 2018
2 parents 110fbb8 + b5ca519 commit a0e5dbc
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 36 deletions.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@

## master

* Renamed `CarPlayManager(_:)` to `CarPlayManager(directions:eventsManager:)`, allowing you to pass in a custom `Directions` object to use when calculating routes. ([#1834](https://github.com/mapbox/mapbox-navigation-ios/pull/1834/))
### CarPlay

* Renamed `CarPlayManager(_:)` to `CarPlayManager(styles:directions:eventsManager:)` and `CarPlayNavigationViewController(with:mapTemplate:interfaceController:manager:styles:)` to `CarPlayNavigationViewController(navigationService:mapTemplate:interfaceController:manager:styles:)`. These initializers now accept an array of `Style` objects to apply throughout the CarPlay interface, similar to `NavigationViewController`. You can also change the styles at any time by setting the `CarPlayManager.styles` property. ([#1836](https://github.com/mapbox/mapbox-navigation-ios/pull/1836))
* `CarPlayManager(styles:directions:eventsManager:)` also allows you to pass in a custom `Directions` object to use when calculating routes. ([#1834](https://github.com/mapbox/mapbox-navigation-ios/pull/1834/))
* Removed the `StyleManager(_:)` initializer. After initializing a `StyleManager` object, set the `StyleManager.delegate` property to ensure that the style manager’s settings take effect. ([#1836](https://github.com/mapbox/mapbox-navigation-ios/pull/1836))
* Some additional members of `CarPlayManager` are now accessible in Objective-C code. ([#1836](https://github.com/mapbox/mapbox-navigation-ios/pull/1836))

### Other changes

* Fixed a crash during turn-by-turn navigation. ([#1820](https://github.com/mapbox/mapbox-navigation-ios/pull/1820))
* Fixed a crash that could happen while simulating a route. ([#1820](https://github.com/mapbox/mapbox-navigation-ios/pull/1820))

Expand Down
55 changes: 45 additions & 10 deletions MapboxNavigation/CarPlayManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,51 @@ public class CarPlayManager: NSObject {
*/
@objc public static var isConnected = false

public let eventsManager: NavigationEventsManager
public let directions: Directions
/**
The events manager used during turn-by-turn navigation while connected to
CarPlay.
*/
@objc public let eventsManager: NavigationEventsManager

/**
The object that calculates routes when the user interacts with the CarPlay
interface.
*/
@objc public let directions: Directions

/**
The styles displayed in the CarPlay interface.
*/
@objc public var styles: [Style] {
didSet {
if let mapViewController = carWindow?.rootViewController as? CarPlayMapViewController {
mapViewController.styles = styles
}
currentNavigator?.styles = styles
}
}

/**
Initializes a new CarPlayManager, which manages a connection to the CarPlay interface.
- parameter directions: An optional directions client. Pass `nil` to use the default shared client.
- parameter eventsManager: And optional events manager. Pass `nil` to use the default events manager.
*/
public init(directions: Directions? = nil, eventsManager: NavigationEventsManager? = nil) {
Initializes a new CarPlay manager that manages a connection to the CarPlay
interface.
- parameter styles: The styles to display in the CarPlay interface. If this
argument is omitted, `DayStyle` and `NightStyle` are displayed by
default.
- parameter directions: The object that calculates routes when the user
interacts with the CarPlay interface. If this argument is `nil` or
omitted, the shared `Directions` object is used by default.
- parameter eventsManager: The events manager to use during turn-by-turn
navigation while connected to CarPlay. If this argument is `nil` or
omitted, a standard `NavigationEventsManager` object is used by default.
*/
@objc public init(styles: [Style]? = nil,
directions: Directions? = nil,
eventsManager: NavigationEventsManager? = nil) {
self.styles = styles ?? [DayStyle(), NightStyle()]
self.directions = directions ?? .shared
self.eventsManager = eventsManager ?? NavigationEventsManager(dataSource: nil)

super.init()
}

Expand Down Expand Up @@ -129,7 +163,7 @@ extension CarPlayManager: CPApplicationDelegate {
UIApplication.shared.isIdleTimerDisabled = true
}

let viewController = CarPlayMapViewController()
let viewController = CarPlayMapViewController(styles: styles)
window.rootViewController = viewController
self.carWindow = window

Expand Down Expand Up @@ -414,10 +448,11 @@ extension CarPlayManager: CPMapTemplateDelegate {
let navigationMapTemplate = self.mapTemplate(forNavigating: trip)
interfaceController.setRootTemplate(navigationMapTemplate, animated: true)

let navigationViewController = CarPlayNavigationViewController(with: service,
let navigationViewController = CarPlayNavigationViewController(navigationService: service,
mapTemplate: navigationMapTemplate,
interfaceController: interfaceController,
manager: self)
manager: self,
styles: styles)
navigationViewController.startNavigationSession(for: trip)
navigationViewController.carPlayNavigationDelegate = self
currentNavigator = navigationViewController
Expand Down
2 changes: 1 addition & 1 deletion MapboxNavigation/CarPlayManagerDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public protocol CarPlayManagerDelegate {
- returns: An array of map buttons to display on the map while `template` is visible.
*/
@objc(carPlayManager:mapButtonsCompatibleWithTraitCollection:inTemplate:forActivity:)
optional func carPlayManager(_ carplayManager: CarPlayManager, mapButtonsCompatibleWith traitCollection: UITraitCollection, in template: CPTemplate, for activity: CarPlayActivity) -> [CPMapButton]?
optional func carPlayManager(_ carPlayManager: CarPlayManager, mapButtonsCompatibleWith traitCollection: UITraitCollection, in template: CPTemplate, for activity: CarPlayActivity) -> [CPMapButton]?


/**
Expand Down
45 changes: 41 additions & 4 deletions MapboxNavigation/CarPlayMapViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,17 @@ class CarPlayMapViewController: UIViewController {

static let defaultAltitude: CLLocationDistance = 16000

var styleManager: StyleManager!
var styleManager: StyleManager?

/**
The interface styles available to `styleManager` for display.
*/
var styles: [Style] {
didSet {
styleManager?.styles = styles
}
}

/// A very coarse location manager used for distinguishing between daytime and nighttime.
fileprivate let coarseLocationManager: CLLocationManager = {
let coarseLocationManager = CLLocationManager()
Expand Down Expand Up @@ -40,6 +50,32 @@ class CarPlayMapViewController: UIViewController {

var styleObservation: NSKeyValueObservation?

/**
Initializes a new CarPlay map view controller.
- parameter styles: The interface styles initially available to the style manager for display.
*/
required init(styles: [Style]) {
self.styles = styles

super.init(nibName: nil, bundle: nil)
}

required init?(coder aDecoder: NSCoder) {
guard let styles = aDecoder.decodeObject(of: [NSArray.self, Style.self], forKey: "styles") as? [Style] else {
return nil
}
self.styles = styles

super.init(coder: aDecoder)
}

override func encode(with aCoder: NSCoder) {
super.encode(with: aCoder)

aCoder.encode(styles, forKey: "styles")
}

override func loadView() {
let mapView = NavigationMapView()
// mapView.navigationMapDelegate = self
Expand All @@ -58,9 +94,10 @@ class CarPlayMapViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

styleManager = StyleManager(self)
styleManager.styles = [DayStyle(), NightStyle()]

styleManager = StyleManager()
styleManager!.delegate = self
styleManager!.styles = styles

resetCamera(animated: false, altitude: CarPlayMapViewController.defaultAltitude)
mapView.setUserTrackingMode(.followWithCourse, animated: true)
Expand Down
29 changes: 22 additions & 7 deletions MapboxNavigation/CarPlayNavigationViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,18 @@ public class CarPlayNavigationViewController: UIViewController {
var carFeedbackTemplate: CPGridTemplate!
var carInterfaceController: CPInterfaceController
var previousSafeAreaInsets: UIEdgeInsets?
var styleManager: StyleManager!
var styleManager: StyleManager?

/**
The interface styles available for display.
These are the styles available to the view controller’s internal `StyleManager` object. In CarPlay, `Style` objects primarily affect the appearance of the map, not guidance-related overlay views.
*/
@objc public var styles: [Style] {
didSet {
styleManager?.styles = styles
}
}

let distanceFormatter = DistanceFormatter(approximate: true)

Expand All @@ -51,17 +62,20 @@ public class CarPlayNavigationViewController: UIViewController {
- parameter mapTemplate: The map template visible during the navigation session.
- parameter interfaceController: The interface controller for CarPlay.
- parameter manager: The manager for CarPlay.
- parameter styles: The interface styles that the view controller’s internal `StyleManager` object can select from for display.
- postcondition: Call `startNavigationSession(for:)` after initializing this object to begin navigation.
*/
@objc(initWithNavigationService:mapTemplate:interfaceController:manager:)
public init(with navigationService: NavigationService,
mapTemplate: CPMapTemplate,
interfaceController: CPInterfaceController, manager: CarPlayManager) {
@objc public init(navigationService: NavigationService,
mapTemplate: CPMapTemplate,
interfaceController: CPInterfaceController,
manager: CarPlayManager,
styles: [Style]? = nil) {
self.navService = navigationService
self.mapTemplate = mapTemplate
self.carInterfaceController = interfaceController
self.carPlayManager = manager
self.styles = styles ?? [DayStyle(), NightStyle()]

super.init(nibName: nil, bundle: nil)
carFeedbackTemplate = createFeedbackUI()
Expand Down Expand Up @@ -97,8 +111,9 @@ public class CarPlayNavigationViewController: UIViewController {
self?.mapView?.recenterMap()
}

styleManager = StyleManager(self)
styleManager.styles = [DayStyle(), NightStyle()]
styleManager = StyleManager()
styleManager!.delegate = self
styleManager!.styles = self.styles

makeGestureRecognizersResetFrameRate()
resumeNotifications()
Expand Down
12 changes: 9 additions & 3 deletions MapboxNavigation/NavigationViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -354,10 +354,15 @@ open class NavigationViewController: UIViewController {
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.
- parameter route: The route to navigate along.
- parameter styles: The styles that the view controller’s internal `StyleManager` object can select from for display.
- parameter navigationService: The navigation service that manages navigation along the route.
- parameter voiceController: The voice controller that manages the delivery of voice instructions during navigation.
*/
@objc(initWithRoute:styles:navigationService:voiceController:)
required public init(for route: Route,
styles: [Style]? = [DayStyle(), NightStyle()],
styles: [Style]? = nil,
navigationService: NavigationService? = nil,
voiceController: RouteVoiceController? = nil) {

Expand Down Expand Up @@ -387,8 +392,9 @@ open class NavigationViewController: UIViewController {
mapSubview.pinInSuperview()
mapViewController.reportButton.isHidden = !showsReportFeedback

self.styleManager = StyleManager(self)
self.styleManager.styles = styles ?? [DayStyle(), NightStyle()]
styleManager = StyleManager()
styleManager.delegate = self
styleManager.styles = 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.")
Expand Down
8 changes: 1 addition & 7 deletions MapboxNavigation/StyleManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,7 @@ open class StyleManager: NSObject {

var currentStyleType: StyleType?

/**
Initializes a new `StyleManager`.
- parameter delegate: The receiver’s delegate
*/
required public init(_ delegate: StyleManagerDelegate) {
self.delegate = delegate
@objc public override init() {
super.init()
resumeNotifications()
resetTimeOfDayTimer()
Expand Down
14 changes: 13 additions & 1 deletion MapboxNavigationTests/CarPlayManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ class CarPlayManagerTests: XCTestCase {

XCTAssertTrue(exampleDelegate.navigationEnded, "The CarPlayManagerDelegate should have been told that navigation ended.")
}

func testDirectionsOverride() {

class DirectionsInvocationSpy: Directions {
Expand Down Expand Up @@ -196,6 +197,17 @@ class CarPlayManagerTests: XCTestCase {

XCTAssert(subject.directions == spy, "Directions client is not overridden properly.")
}

func testCustomStyles() {
class CustomStyle: DayStyle {}

XCTAssertEqual(manager?.styles.count, 2)
XCTAssertEqual(manager?.styles.first?.styleType, StyleType.day)
XCTAssertEqual(manager?.styles.last?.styleType, StyleType.night)

let styles = [CustomStyle()]
XCTAssertEqual(CarPlayManager(styles: styles).styles, styles, "CarPlayManager should persist the initial styles given to it.")
}
}


Expand Down Expand Up @@ -313,7 +325,7 @@ class TestCarPlayManagerDelegate: CarPlayManagerDelegate {
return trailingBarButtons
}

func carPlayManager(_ carplayManager: CarPlayManager, mapButtonsCompatibleWith traitCollection: UITraitCollection, in template: CPTemplate, for activity: CarPlayActivity) -> [CPMapButton]? {
func carPlayManager(_ carPlayManager: CarPlayManager, mapButtonsCompatibleWith traitCollection: UITraitCollection, in template: CPTemplate, for activity: CarPlayActivity) -> [CPMapButton]? {
return mapButtons
}

Expand Down
3 changes: 2 additions & 1 deletion MapboxNavigationTests/StyleManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ class StyleManagerTests: XCTestCase {

override func setUp() {
super.setUp()
styleManager = StyleManager(self)
styleManager = StyleManager()
styleManager.delegate = self
styleManager.automaticallyAdjustsStyleForTimeOfDay = true
}

Expand Down
2 changes: 1 addition & 1 deletion TestHelper/Fixture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class Fixture: NSObject {
let filePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(fileName)

_ = directions.calculate(options, completionHandler: { (waypoints, routes, error) in
guard let route = routes?.first else { return }
guard let _ = routes?.first else { return }
print("Route downloaded to \(filePath)")
completion()
})
Expand Down

0 comments on commit a0e5dbc

Please sign in to comment.