Skip to content

Add CarPlay example. #134

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import CarPlay

// MARK: - CPTemplateApplicationSceneDelegate methods

extension AppDelegate: 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.carPlayManager.application(UIApplication.shared,
didConnectCarInterfaceController: interfaceController,
to: window)

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.delegate = nil

appDelegate.carPlayManager.application(UIApplication.shared,
didDisconnectCarInterfaceController: interfaceController,
from: window)

appDelegate.carPlayManager.templateApplicationScene(templateApplicationScene,
didDisconnectCarInterfaceController: interfaceController,
from: window)
}
}
219 changes: 219 additions & 0 deletions CarPlayExample/AppDelegate+CarPlayManagerDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import CarPlay
import MapboxCoreNavigation
import MapboxNavigation
import MapboxDirections
import MapboxMaps

// MARK: - CarPlayManagerDelegate methods

extension AppDelegate: CarPlayManagerDelegate {

// Delegate method, which allows to provide list of leading `CPBarButton`s for specific `CarPlayActivity`.
// It's possible to provide up to two leading `CPBarButton`s.
func carPlayManager(_ carPlayManager: CarPlayManager,
leadingNavigationBarButtonsCompatibleWith traitCollection: UITraitCollection,
in carPlayTemplate: CPTemplate,
for activity: CarPlayActivity) -> [CPBarButton]? {
guard let interfaceController = self.carPlayManager.interfaceController else { return nil }

switch activity {

case .browsing:
let searchTemplate = CPSearchTemplate()
searchTemplate.delegate = carPlaySearchController
let searchButton = carPlaySearchController.searchTemplateButton(searchTemplate: searchTemplate,
interfaceController: interfaceController,
traitCollection: traitCollection)
return [searchButton]
case .panningInBrowsingMode:
break

case .panningInNavigationMode:
break

case .previewing:
break

case .navigating:
break
}

return []
}

// Delegate method, which allows to provide list of trailing `CPBarButton`s for specific `CarPlayActivity`.
// It's possible to provide up to two trailing `CPBarButton`s.
func carPlayManager(_ carPlayManager: CarPlayManager,
trailingNavigationBarButtonsCompatibleWith traitCollection: UITraitCollection,
in carPlayTemplate: CPTemplate,
for activity: CarPlayActivity) -> [CPBarButton]? {
switch activity {

case .browsing:
break

case .panningInBrowsingMode:
break

case .panningInNavigationMode:
break

case .previewing:
break

case .navigating:
return [carPlayManager.exitButton]
}

return []
}

// Delegate method, which allows to provide a list of `CPMapButton`, which are shown on a map.
// It's possible to provide up to four `CPMapButton`s.
func carPlayManager(_ carPlayManager: CarPlayManager,
mapButtonsCompatibleWith traitCollection: UITraitCollection,
in carPlayTemplate: CPTemplate,
for activity: CarPlayActivity) -> [CPMapButton]? {
switch activity {

case .browsing:
break

case .panningInBrowsingMode:
break

case .panningInNavigationMode:
break

case .previewing:
break

case .navigating:
break
}

return []
}

func carPlayManager(_ carPlayManager: CarPlayManager,
didFailToFetchRouteBetween waypoints: [Waypoint]?,
options: RouteOptions,
error: DirectionsError) -> CPNavigationAlert? {
let alertAction = CPAlertAction(title: "Dismiss", style: .default, handler: { _ in })

let navigationAlert = CPNavigationAlert(titleVariants: ["Failed to fetch"],
subtitleVariants: nil,
image: nil,
primaryAction: alertAction,
secondaryAction: nil,
duration: 2.5)

return navigationAlert
}

func carPlayManager(_ carPlayManager: CarPlayManager,
willPreview trip: CPTrip) -> CPTrip {
return trip
}

func carPlayManager(_ carPlayManager: CarPlayManager,
willPreview trip: CPTrip,
with previewTextConfiguration: CPTripPreviewTextConfiguration) -> CPTripPreviewTextConfiguration {
return previewTextConfiguration
}

func carPlayManager(_ carPlayManager: CarPlayManager,
selectedPreviewFor trip: CPTrip,
using routeChoice: CPRouteChoice) {

}

func carPlayManager(_ carPlayManager: CarPlayManager,
didBeginNavigationWith service: NavigationService) {

}

// Delegate method, which is called after ending active-guidance navigation session and dismissing
// `CarPlayNavigationViewController`.
func carPlayManagerDidEndNavigation(_ carPlayManager: CarPlayManager) {
let alertAction = CPAlertAction(title: "OK",
style: .default,
handler: { [weak self] _ in
self?.carPlayManager.interfaceController?.dismissTemplate(animated: true)
})

let alertTemplate = CPAlertTemplate(titleVariants: ["Did end active-guidance navigation."],
actions: [alertAction])

carPlayManager.interfaceController?.presentTemplate(alertTemplate, animated: true)
}

// Delegate method, which allows to show `CPActionSheetTemplate` or `CPAlertTemplate`
// after arriving to the specific `Waypoint`.
func carPlayManager(_ carPlayManager: CarPlayManager,
shouldPresentArrivalUIFor waypoint: Waypoint) -> Bool {
return true
}

// Delegate method, which provides the ability to disable the idle timer to avert system sleep.
func carPlayManagerShouldDisableIdleTimer(_ carPlayManager: CarPlayManager) -> Bool {
return true
}

// Delegate method, which is called right after starting active-guidance navigation and presenting
// `CarPlayNavigationViewController`.
func carPlayManager(_ carPlayManager: CarPlayManager,
didPresent navigationViewController: CarPlayNavigationViewController) {
let alertAction = CPAlertAction(title: "OK",
style: .default,
handler: { [weak self] _ in
self?.carPlayManager.interfaceController?.dismissTemplate(animated: true)
})

let alertTemplate = CPAlertTemplate(titleVariants: ["Did present CarPlayNavigationViewController."],
actions: [alertAction])

carPlayManager.interfaceController?.presentTemplate(alertTemplate, animated: true)
}

// Delegate method, which allows to modify final destination annotation whenever its added to
// `CarPlayMapViewController` or `CarPlayNavigationViewController`.
func carPlayManager(_ carPlayManager: CarPlayManager,
didAdd finalDestinationAnnotation: PointAnnotation,
to parentViewController: UIViewController,
pointAnnotationManager: PointAnnotationManager) {
var finalDestinationAnnotation = finalDestinationAnnotation
if let image = UIImage(named: "marker") {
finalDestinationAnnotation.image = .init(image: image, name: "marker")
} else {
let image = UIImage(named: "default_marker", in: .mapboxNavigation, compatibleWith: nil)!
finalDestinationAnnotation.image = .init(image: image, name: "marker")
}

pointAnnotationManager.annotations = [finalDestinationAnnotation]
}

func carPlayManager(_ carPlayManager: CarPlayManager,
templateWillAppear template: CPTemplate,
animated: Bool) {

}

func carPlayManager(_ carPlayManager: CarPlayManager,
templateDidAppear template: CPTemplate,
animated: Bool) {

}

func carPlayManager(_ carPlayManager: CarPlayManager,
templateWillDisappear template: CPTemplate,
animated: Bool) {

}

func carPlayManager(_ carPlayManager: CarPlayManager,
templateDidDisappear template: CPTemplate,
animated: Bool) {

}
}
126 changes: 126 additions & 0 deletions CarPlayExample/AppDelegate+CarPlaySearchControllerDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import CarPlay
import MapboxNavigation
import MapboxDirections
import MapboxGeocoder

// MARK: - CarPlaySearchControllerDelegate methods

extension AppDelegate: CarPlaySearchControllerDelegate {

func previewRoutes(to waypoint: Waypoint, completionHandler: @escaping () -> Void) {
carPlayManager.previewRoutes(to: waypoint, completionHandler: completionHandler)
}

func resetPanButtons(_ mapTemplate: CPMapTemplate) {
carPlayManager.resetPanButtons(mapTemplate)
}

func pushTemplate(_ template: CPTemplate, animated: Bool) {
if let listTemplate = template as? CPListTemplate {
listTemplate.delegate = carPlaySearchController
}
carPlayManager.interfaceController?.pushTemplate(template, animated: animated)
}

func popTemplate(animated: Bool) {
carPlayManager.interfaceController?.popTemplate(animated: animated)
}

func recentSearches(with searchText: String) -> [CPListItem] {
if searchText.isEmpty {
return recentItems.map { $0.navigationGeocodedPlacemark.listItem() }
}

return recentItems.filter {
$0.matches(searchText)
}.map {
$0.navigationGeocodedPlacemark.listItem()
}
}

func searchResults(with items: [CPListItem], limit: UInt?) -> [CPListItem] {
recentSearchItems = items

if items.count > 0 {
if let limit = limit {
return Array<CPListItem>(items.prefix(Int(limit)))
}

return items
} else {
let noResultListItem = CPListItem(text: "No results",
detailText: nil,
image: nil,
showsDisclosureIndicator: false)

return [noResultListItem]
}
}

func searchTemplate(_ searchTemplate: CPSearchTemplate,
updatedSearchText searchText: String,
completionHandler: @escaping ([CPListItem]) -> Void) {
recentSearchText = searchText

var items = recentSearches(with: searchText)
let limit: UInt = 2

if searchText.count > 2 {

let forwardGeocodeOptions = ForwardGeocodeOptions(query: searchText)
forwardGeocodeOptions.locale = Locale.autoupdatingCurrent.languageCode == "en" ? nil : .autoupdatingCurrent

var allowedScopes: PlacemarkScope = .all
allowedScopes.remove(.postalCode)

forwardGeocodeOptions.allowedScopes = allowedScopes
forwardGeocodeOptions.maximumResultCount = 10
forwardGeocodeOptions.includesRoutableLocations = true

Geocoder.shared.geocode(forwardGeocodeOptions,
completionHandler: { [weak self] (placemarks, attribution, error) in
guard let self = self else {
completionHandler([])
return
}

guard let placemarks = placemarks else {
completionHandler(self.searchResults(with: items, limit: limit))
return
}

let navigationGeocodedPlacemarks = placemarks.map {
NavigationGeocodedPlacemark(title: $0.formattedName,
subtitle: $0.address,
location: $0.location,
routableLocations: $0.routableLocations)
}

let results = navigationGeocodedPlacemarks.map { $0.listItem() }
items.append(contentsOf: results)
completionHandler(self.searchResults(with: results, limit: limit))
})
} else {
completionHandler(self.searchResults(with: items, limit: limit))
}
}

func searchTemplate(_ searchTemplate: CPSearchTemplate,
selectedResult item: CPListItem,
completionHandler: @escaping () -> Void) {
guard let userInfo = item.userInfo as? [String: Any],
let placemark = userInfo[CarPlaySearchController.CarPlayGeocodedPlacemarkKey] as? NavigationGeocodedPlacemark,
let location = placemark.routableLocations?.first ?? placemark.location else {
completionHandler()
return
}

recentItems.add(RecentItem(placemark))
recentItems.save()

let destinationWaypoint = Waypoint(location: location,
heading: nil,
name: placemark.title)
previewRoutes(to: destinationWaypoint, completionHandler: completionHandler)
}
}
Loading