Powered by Zoomin Software. For more details please contactZoomin

HERE SDK for iOS (Explore Edition) - Developer Guide

Product category
Technology
Doc type
Version
Product lifecycle
This publication

HERE SDK for iOS (Explore Edition) - Developer Guide: Integration with CarPlay

Integration with 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.

Note

Please note that while CarPlay may function as expected, its compatibility is not guaranteed, as this dependency is neither managed nor tested by the HERE SDK team.

How does it work?

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.

A general CarPlay integration may look like below:

  1. Configure CarPlay in Info.plist to handle CarPlay scenes and controllers.
  2. In CarPlaySceneDelegate class, implement CPTemplateApplicationSceneDelegate to manage CarPlay lifecycle events.
  3. In iPhoneSceneDelegate class, implement UIWindowSceneDelegate to manages the lifecycle events of a UI scene for the mobile application.
  4. Create a CarPlayViewController to manage the map display on CarPlay and ViewController to manage the main view controller shown on the mobile device.
  5. Initialize and configure a map in the CarPlay environment.
  6. Build and run your app as usual. In addition: Launch 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

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: Update the Info.plist

Add the following to your Info.plist to enable the CarPlay scene configuration:

<!-- Scene configuration -->
<key>UIApplicationSceneManifest</key>
<dict>
    <key>UIApplicationSupportsMultipleScenes</key>
    <true/>
    <key>UISceneConfigurations</key>
    <dict>
        <!-- iPhone and iPad Scene Configuration -->
        <key>UIWindowSceneSessionRoleApplication</key>
        <array>
            <dict>
                <key>UISceneClassName</key>
                <string>UIWindowScene</string>
                <key>UISceneDelegateClassName</key>
                <string>$(PRODUCT_MODULE_NAME).iPhoneSceneDelegate</string>
                <key>UISceneConfigurationName</key>
                <string>Default Configuration</string>
            </dict>
        </array>
        <!-- CarPlay Scene Configuration -->
        <key>CPTemplateApplicationSceneSessionRoleApplication</key>
        <array>
            <dict>
                <key>UISceneClassName</key>
                <string>CPTemplateApplicationScene</string>
                <key>UISceneDelegateClassName</key>
                <string>$(PRODUCT_MODULE_NAME).CarPlaySceneDelegate</string>
                <key>UISceneConfigurationName</key>
                <string>Default CarPlay Configuration</string>
            </dict>
        </array>
    </dict>
</dict>

Step 3 - Create a CarPlay scene delegate

Adapt class CarPlaySceneDelegate to conform CPTemplateApplicationSceneDelegate protocol to manage the lifecycle events for the CarPlay scenes. CPTemplateApplicationSceneDelegate is responsible for setting up the user interface in CarPlay and handling the transitions between different states of the application when used in a CarPlay environment. This class is specified in the Info.plist under the CPTemplateApplicationSceneSessionRoleApplication key and gets called when the app interacts with CarPlay.

CPTemplateApplicationSceneDelegate 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. The CPTemplateApplicationSceneDelegate 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.

Similar to our view controller, we set the CPMapTemplate instance as root template.

Below is the implementation of CarPlaySceneDelegate:

// `CarPlaySceneDelegate` manages the lifecycle events for the CarPlay scenes.
class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate {
    var interfaceController: CPInterfaceController?
    var carPlayWindow: CPWindow?
    var carPlayMapTemplate = CPMapTemplate()
    let carPlayViewController = CarPlayViewController()

    /// Conform to `CPTemplateApplicationSceneDelegate`, needed for CarPlay.
    /// Called when the CarPlay interface controller connects and a new window for CarPlay is created.
    /// Initializes the view controller for CarPlay and sets up the root template with necessary UI elements.
    func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene,
                                  didConnect interfaceController: CPInterfaceController,
                                  to window: CPWindow) {
        self.interfaceController = interfaceController
        self.carPlayWindow = window

        // 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)
        // CarPlayViewController is main view controller for the provided CPWindow.
        window.rootViewController = carPlayViewController
    }

    /// Conform to `CPTemplateApplicationSceneDelegate`, needed for CarPlay.
    /// Called when the CarPlay interface is disconnected.
    /// Use this method to clean up resources related to the CarPlay interface.
    func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene,
                                  didDisconnect interfaceController: CPInterfaceController,
                                  from window: CPWindow) {
        // Handle disconnection from CarPlay.
    }

    // Helper method to create navigation buttons on the CarPlay interface.
    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
    }
}

Step 4 - Create a phone scene delegate

Implement UIWindowSceneDelegate in iPhoneSceneDelegate class to manage the lifecycle events of a UI scene for the application. UIWindowSceneDelegate handles the setup and tear-down of the application's window and its content, responding to state transitions within the scene. The scene management includes creating and dismissing the window, transitioning the application between background and foreground, and handling configuration changes. It is specified in the Info.plist under the UISceneConfigurations key for standard iOS user interfaces. Below is the implementation of iPhoneSceneDelegate:

// `iPhoneSceneDelegate` manages the lifecycle events of a UI scene for the application.
class iPhoneSceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    /// Called when a new scene session is being created and associated with the app.
    /// This method sets up the initial content and configuration for the scene using either Storyboards or programmatically via SwiftUI (not shown here).
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply that the connecting scene or the session are new (see `application(_:configurationForConnecting:options:)`).

        guard let windowScene = scene as? UIWindowScene else { return }

        // If using Storyboards
        window = UIWindow(windowScene: windowScene)
        window?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
        window?.makeKeyAndVisible()
    }
}

Step 5 - Create a CarPlay view controller

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: .distanceInMeters, 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.

Now, click Build to 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 and iPhone simulator.

Try the CarPlay example app

The finished "HelloMapCarPlay" example app can be found on GitHub.

Next steps

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.

Was this article helpful?
TitleResults for “How to create a CRG?”Also Available inAlert