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:

  1. In your AppDelegate, implement the CPApplicationDelegate protocol to get a CPWindow.
  2. Create a UIViewController and set it as root to the CPWindow.
  3. 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.

  1. In Xcode, create a new Property List file with the name Entitlements.plist.
  2. 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

// This is the view controller shown on an in car's head unit display with CarPlay.
class CarPlayViewController: UIViewController {

    var mapView : MapView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Initialize MapView without a storyboard.
        mapView = MapView(frame: view.bounds)
        view.addSubview(mapView)

        // Load the map scene using a map scheme to render the map with.
        mapView.mapScene.loadScene(mapScheme: MapScheme.normalDay, completion: onLoadScene)
    }

    // Completion handler when loading a map scene.
    private func onLoadScene(mapError: MapError?) {
        guard mapError == nil else {
            print("Error: Map scene not loaded, \(String(describing: mapError))")
            return
        }

        // Configure the map.
        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 that you can also pin images as an overlay to a MapScene with the class MapImageOverlay that allows to show bitmap graphics on top of the map view that do not move, scale or tilt together with the map.

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()

    // Conform to CPApplicationDelegate, needed for CarPlay.
    func application(_ application: UIApplication,
                     didConnectCarInterfaceController interfaceController: CPInterfaceController,
                     to window: CPWindow) {
        // CarPlay window has been connected. Set up the view controller for it and a map template.
        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
    }

    // Conform to CPApplicationDelegate, needed for CarPlay.
    func application(_ application: UIApplication,
                     didDisconnectCarInterfaceController interfaceController: CPInterfaceController,
                     from window: CPWindow) {
        // Override point for customization when disconnecting from car interface.
    }

    ...
}

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.

results matching ""

    No results matching ""