Tutorial: Integrate a HERE Map into CarPlay
This tutorial shows how a MapView
can be rendered in your car's infotainment display with Apple's CarPlay. CarPlay allows to run an app on the actual hardware of a car. It can be simulated using a CarPlay simulator running on your development machine along your iOS simulator side-by-side.
The resulting app of this tutorial will show the MapView
instance on an in-car's head unit display with a zoom-in button and a zoom-out button. Another MapView
instance will be shown on the screen of the connected mobile device or simulator. The finished HelloMapCarPlay example app can be found on GitHub.
At a glance
The HERE SDK does not require any special set-up. It can be used together with CarPlay like any other API by following the CarPlay developer guides. As useful resources, use also the CarPlay API Reference and the CarPlay Programming Guide which are provided by Apple.
These basic steps are common for every app:
- In your
AppDelegate
, implement the CPApplicationDelegate
protocol to get a CPWindow
. - Create a
UIViewController
and set it as root to the CPWindow
. - Build and run your app as usual. In addition: Launch the the CarPlay Simulator from the simulator's main menu via I/O -> External Displays -> CarPlay. As a result, you can see the iPad or iPhone simulator in one window and the CarPlay simulator in a second window.
Your UIViewController
does not need to meet any specific requirements, except that it needs to follow Apple's design guidelines for in-car use.
In order to distribute your app or to deploy it onto a real device, your app needs to be reviewed by Apple. For this, you need to create a CarPlay app entitlement and agree to the CarPlay Entitlement Addendum.
Note
Until the entitlement is reviewed by Apple, a CarPlay app cannot be installed on a real device. Use an iOS device simulator instead. For deployment on a real device - even for testing purposes - a provisioning profile is needed that is enabled for the entitlement.
Integrate CarPlay into an existing HERE SDK app
Let's take a closer look and create a new CarPlay app.
For this tutorial, we use the HelloMap app you can find on GitHub. By default, it shows a MapView
on the device. Now we want to extend this app to show a second instance of a MapView
on the in-car head unit display. For this, we need to integrate CarPlay.
The MapView
will behave like a normal MapView
: You can customize the look, add 3D objects onto it, or use any other HERE SDK engine along with it - however, it does not yet provide support for map gestures. Therefore, we will need to add buttons to interact with the map.
Note
The resulting app is only meant for testing purposes. For a production-ready app, make sure to follow the CarPlay design guidelines to see how to design an app that is relevant to the car environment.
Step 1 - Create a CarPlay entitlement
For this step, we need to create an Entitlements.plist
file. It specifies the capabilities of our app and is bound to our App ID. It can be created with Xcode. Make sure to follow the process as described here: For an appropriate entitlement, you need to contact Apple. For the below testing setup via simulator, this is not yet needed.
- In Xcode, create a new Property List file with the name
Entitlements.plist
. - Under Build Settings -> Code Signing Entitlements set the path to "HelloMapCarPlay/Entitlements.plist".
Make sure that the file is located at this location: HelloMapCarPlay/HelloMapCarPlay/Entitlements.plist
- here, "HelloMapCarPlay" is our project name, inside which is another folder of the same name: Add the file to this folder.
Edit the Entitlements.plist
file to look like this (adapt it later to meet the actual requirements of your app):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.carplay-maps</key>
<true/>
</dict>
</plist>
The com.apple.developer.carplay-maps
key indicates an application scope that is needed for turn-by-turn navigation apps.
Step 2 - Create a UIViewController
We use a UIViewController
to show a MapView
. Our HelloMap example app already contains a ViewController
class which shows a MapView
. Now, we create a second one which is only instantiated to be used by CarPlay. However, code-wise, the class looks exactly the same:
import heresdk
import UIKit
class CarPlayViewController: UIViewController {
var mapView : MapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView = MapView(frame: view.bounds)
view.addSubview(mapView)
mapView.mapScene.loadScene(mapScheme: MapScheme.normalDay, completion: onLoadScene)
}
private func onLoadScene(mapError: MapError?) {
guard mapError == nil else {
print("Error: Map scene not loaded, \(String(describing: mapError))")
return
}
let camera = mapView.camera
let distanceInMeters = MapMeasure(kind: .distance, value: 1000 * 7)
camera.lookAt(point: GeoCoordinates(latitude: 52.518043, longitude: 13.405991), zoom: distanceInMeters)
}
public func zoomIn() {
mapView.camera.zoomBy(2, around: mapView!.camera.principalPoint)
}
public func zoomOut() {
mapView.camera.zoomBy(0.5, around: mapView!.camera.principalPoint)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
mapView.handleLowMemory()
}
}
With this new view controller, we create a second MapView
instance. And we add two zoom methods zoomIn()
and zoomOut()
to show how a basic interaction with the map can be implemented. In a next step, we create two buttons that will be shown in the head unit's display to call both methods.
You can add more buttons to interact with the map, e.g. for panning. However, make sure to follow Apple's design guidelines to not distract the driver's attention.
Note
The HERE SDK does not yet support map gestures for CarPlay. Therefore, buttons need to be used to interact with the map. On top, not all cars support touch gestures such as pan movements. Read the CarPlay Programming Guide to learn more about gesture panning through knob and touch pad events.
Step 3 - Get a CarPlay window
In order to show the MapView
, we need to set our new CarPlayViewController
as root.
Open the AppDelegate
class and add the following imports:
import CarPlay
import heresdk
import UIKit
Then we adapt the class to conform to the CPApplicationDelegate
protocol. It requires us to implement two methods. The first one notifies when the mobile device is connected to the head unit's display - and thus, we receive a CarPlay window to show content. The second one notifies, when the device is disconnected:
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CPApplicationDelegate {
var window: UIWindow?
let carPlayViewController = CarPlayViewController()
let carPlayMapTemplate = CPMapTemplate()
func application(_ application: UIApplication,
didConnectCarInterfaceController interfaceController: CPInterfaceController,
to window: CPWindow) {
carPlayMapTemplate.leadingNavigationBarButtons = [createButton(title: "Zoom +"),
createButton(title: "Zoom -")]
interfaceController.setRootTemplate(carPlayMapTemplate, animated: true)
window.rootViewController = carPlayViewController
}
private func createButton(title: String) -> CPBarButton {
let barButton = CPBarButton(type: .text) { (button) in
if (title == "Zoom +") {
self.carPlayViewController.zoomIn()
} else if (title == "Zoom -") {
self.carPlayViewController.zoomOut()
}
}
barButton.title = title
return barButton
}
func application(_ application: UIApplication,
didDisconnectCarInterfaceController interfaceController: CPInterfaceController,
from window: CPWindow) {
}
...
}
The CPApplicationDelegate
allows us to receive a CPWindow
for which we set our CarPlayViewController
instance as rootViewController
. This will be the base view to manage the content in our CarPlay window.
On top, we create a CPMapTemplate
to define two buttons of type CPBarButton
. Apple's CPMapTemplate
also allows to receive and handle touch events. On a button click, we forward the event to our view controller to zoom the MapView
.
Similarly to our view controller, we set the CPMapTemplate
instance as root template.
Note that we keep the rest of the AppDelegate
class untouched.
Alternatively, you can follow Displaying Content in CarPlay guide, to use CarPlay scenes.
Now, click build & run a simulator. After the simulator has launched, open the simulator's app menu and choose I/O -> External Displays -> CarPlay. A second window opens and shows our app on the CarPlay home screen.
The resulting app looks like this:
Screenshot: Showing the HERE Map running on a CarPlay simulator.
Where to go next?
In this tutorial, we showed you how to add support for CarPlay to an existing app. Although we run two separate MapView
instances - one on the device and one on the head unit, you can handover functionality between the device and the head unit - since both MapView
instances run within the same app lifecycle. For example, a user can start route planning on a mobile device at home - then connect the device via USB in-car and start driving (if your app uses the Navigate Edition). The navigation progress can be shown on the car's head unit, while the device can show supportive information like maneuver details.
Make sure to not distract the driver with too much information: So, why not implementing voice controls on top to interact with the app? Some more ideas:
- Add a speed warner assistant, that shows the current valid speed limits on the head's unit display and warns acoustically when you exceed it.
- Show supporting road attribute information for the current road you are driving on.
- Implement an app that shows places information of POIs nearby, like fuel stations, restaurants or sightseeing spots.