ガイド変更履歴HERE SDK API references
ガイド

CarPlayとの統合

このチュートリアルでは、AppleのCarPlayを使用して、乗用車のインフォテイメントディスプレイにMapViewをレンダリングする方法を説明します。CarPlay を使うと、乗用車の実際のハードウェアでアプリを実行できます。これは、開発マシンで実行されている CarPlay シミュレーターを使用して、iOS シミュレーターと並列でシミュレーションを実行できます。

このチュートリアルに従って作成されるアプリは、車載ヘッド ユニット ディスプレイに MapView インスタンスを表示し、ズームイン ボタンとズームアウト ボタンを使用します。別のMapViewインスタンスは、接続されているモバイル機器またはシミュレーターの画面に表示されます。完成した「HelloMapCarPlay」サンプルアプリはGitHubで確認できます。

CarPlay が期待どおりに機能したとしても、この依存関係は HERE SDK チームによって管理およびテストされていないため、互換性は保証されません。

仕組み

HERE SDK は特別な設定は必要ありません。CarPlay 開発者ガイドに従って、他の API と同様に CarPlay と連携できます。便利なリソースとして、Apple から提供されている CarPlay API リファレンスCarPlay プログラミング ガイドもご利用ください。

一般的な CarPlay 統合は次のようになります。

  1. Info.plist に CarPlay を設定して CarPlay シーンとコントローラーを処理します。
  2. CarPlaySceneDelegate クラスで、CPTemplateApplicationSceneDelegate を実装して CarPlay ライフサイクル イベントを管理します。
  3. iPhoneSceneDelegate クラスで、UIWindowSceneDelegate を実装してモバイル アプリケーションの UI シーンのライフサイクル イベントを管理します。
  4. CarPlayViewController を作成し CarPlay および ViewController に表示される地図を管理して、モバイル機器に表示されるメインのビュー コントローラーを管理します。
  5. CarPlay 環境で地図を初期化して設定します。
  6. 通常どおりにアプリを構築し実行します。さらに、[I/O] - > [External Displays](外部ディスプレイ) - > [CarPlay] を選択して、シミュレーターのメイン メニューから CarPlay シミュレーターを起動します。これにより、iPad または iPhone シミュレーターを一方のウィンドウに表示し、CarPlay シミュレーターを 2 つ目のウィンドウに表示できます。

車載用の Apple の設計ガイドラインに従う必要がある場合を除いて、UIViewController で特定の要件を満たす必要はありません。

アプリを配布したり、実際にデバイスにデプロイしたりするには、Apple によるアプリの審査が必要です。これには、CarPlay アプリのエンタイトルメントを作成し、CarPlay Entitlement Addendum に同意する必要があります。

Apple によってエンタイトルメントが審査されるまで、CarPlay アプリを実際のデバイスにインストールすることはできません。代わりに iOS デバイス シミュレーターを使用します。実際のデバイスへのデプロイには、テスト目的であっても、エンタイトルメントで有効になっているプロビジョニング プロフィールが必要です。

CarPlay を統合する

詳細を確認し、新しい CarPlay アプリを作成しましょう。

このチュートリアルでは、GitHubにあるHelloMapアプリを使用します。デフォルトでは、デバイスにMapViewが表示されます。次に、このアプリを拡張して、車載ヘッド ユニット ディスプレイに MapView の 2 つ目のインスタンスを表示します。これを行うには、CarPlay を統合する必要があります。

MapView は標準の MapView のように次のように動作します。外観をカスタマイズしたり、3D オブジェクトを追加したり、他の HERE SDK エンジンを連動して使用したりできますが、マップ ジェスチャーはまだサポートされません。そのため、ボタンを追加して地図を操作する必要があります。

結果として得られるアプリはテストのみを目的とするものです。本番環境で使用できるアプリについては、CarPlay の設計ガイドラインに従って、車両環境に関連するアプリの設計方法を確認してください。

ステップ 1 - CarPlay エンタイトルメントを作成する

このステップでは、Entitlements.plist ファイルを作成する必要があります。これはアプリの機能を指定し、アプリ ID にバインドされます。Xcode で作成できます。こちらで説明されているプロセスに従ってください。該当するエンタイトルメントについては、Apple に連絡する必要があります。以下のシミュレーターによるテスト設定では、これはまだ必要ありません。

  1. Xcode で、Entitlements.plist という名前で新しいプロパティ リスト ファイルを作成します。
  2. [Build Settings](構築設定) -> [Code Signing Entitlements](コード署名エンタイトルメント) で、パスを HelloMapCarPlay/Entitlements.plist に設定します。

ファイルが次の場所にあることを確認します:HelloMapCarPlay/HelloMapCarPlay/Entitlements.plist - ここで、「HelloMapCarPlay」はプロジェクト名で、その中に同じ名前の別のフォルダーがあります。このフォルダーにファイルを追加します。

Entitlements.plist ファイルを以下のように編集します (後でアプリケーションの実際の要件に合わせて調整します)。

<?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>

com.apple.developer.carplay-maps キーは、ターン・バイ・ターンナビ アプリで必要となるアプリケーション スコープを示します。

ステップ2:Info.plist を更新する

次を Info.plist に追加し CarPlay シーン設定を有効にします。


<key>UIApplicationSceneManifest</key>
<dict>
    <key>UIApplicationSupportsMultipleScenes</key>
    <true/>
    <key>UISceneConfigurations</key>
    <dict>
        
        <key>UIWindowSceneSessionRoleApplication</key>
        <array>
            <dict>
                <key>UISceneConfigurationName</key>
                <string>Default Configuration</string>
                <key>UISceneClassName</key>
                <string>UIWindowScene</string>
            </dict>
        </array>

        
        <key>CPTemplateApplicationSceneSessionRoleApplication</key>
        <array>
            <dict>
                <key>UISceneClassName</key>
                <string>CPTemplateApplicationScene</string>
                <key>UISceneConfigurationName</key>
                <string>Default CarPlay Configuration</string>
                <key>UISceneDelegateClassName</key>
                <string>$(PRODUCT_MODULE_NAME).CarPlaySceneDelegate</string>
            </dict>
        </array>
    </dict>
</dict>
<key>UIRequiresFullScreen</key>
<true/>

<key>UILaunchScreen</key>
<dict>
    <key>UIColorName</key>
    <string>White</string>
</dict>

ステップ 3 - CarPlay シーン デリゲートを作成する

クラス CarPlaySceneDelegate を適用し CPTemplateApplicationSceneDelegate プロトコルに準拠して、CarPlay シーンのライフサイクル イベントを管理します。 CPTemplateApplicationSceneDelegate は CarPlay でユーザー インターフェースを設定し、CarPlay 環境で使用する際にアプリケーションのさまざまな状態間の遷移を処理します。このクラスはInfo.plistCPTemplateApplicationSceneSessionRoleApplicationキーの配下に指定され、アプリがCarPlayとやり取りをする時に呼び出されます。

CPTemplateApplicationSceneDelegate は 2 つのメソッドを実装する必要があります。1 つ目のメソッドは、モバイル機器がヘッド ユニットのディスプレイに接続されると通知し、これにより CarPlay ウィンドウを受信してコンテンツを表示できます。2 つ目のメソッドは、デバイスが切断されると通知します。 CPTemplateApplicationSceneDelegate により CarPlayViewController インスタンスを rootViewController として設定する CPWindow を受信できるようになり ます。これは、CarPlay ウィンドウのコンテンツを管理する基本ビューになります。

これに加え、CPMapTemplate を作成し 2 つの CPBarButton タイプのボタンを定義します。Apple の CPMapTemplate でも、タッチ イベントを受け取り、処理できます。ボタンをクリックすると、イベントがビュー コントローラーに転送され、MapView がズームします。

ビュー コントローラーと同様に、CPMapTemplate インスタンスをルート テンプレートとして設定します。

以下は 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
    }
}

ステップ 4 -電話シーン デリゲートを作成する

iPhoneSceneDelegate クラスに UIWindowSceneDelegate を実装し、アプリケーションの UI シーンのライフサイクル イベントを管理します。 UIWindowSceneDelegate はアプリケーションのウィンドウとそのコンテンツの設定と分解を処理し、シーン内の状態遷移に応答します。シーン管理には、ウィンドウの作成と削除、バックグラウンドとフォアグラウンド間のアプリケーションの移行、設定変更の処理があります。これは標準の iOS ユーザー インターフェースの UISceneConfigurations キーの Info.plist で指定されます。 以下は 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()
    }
}

ステップ 5 - CarPlay ビュー コントローラーを作成する

UIViewController を使用して MapView を表示します。「HelloMap」サンプル アプリには、MapView を表示する ViewController クラスがすでに含まれています。次に、CarPlay で使用するためにのみインスタンス化される 2 つ目のインスタンスを作成します。ただし、コードとしては、次とまったく同じクラスのようになります。

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

この新しいビュー コントローラーを使って、2 つ目の MapView インスタンスを作成します。また、2 つのズーム メソッド zoomIn()zoomOut() を追加し、地図に実装できる基本的な操作を示します。次のステップでは、ヘッド ユニット ディスプレイに表示される 2 つのボタンを作成し、両方のメソッドを呼び出します。

パンなどの地図を操作するためのボタンを追加できます。ただし、Apple の設計ガイドラインに従って、ドライバーの注意をそらすことがないようにします。また、MapImageOverlay クラスで画像をオーバーレイとして MapScene にピン留めして、マップ ビューに地図と一緒に移動、拡大縮小、傾斜しないビットマップ グラフィックを表示できます。

HERE SDK は、CarPlay のマップ ジェスチャーはサポートしません。そのため、ボタンを使用して地図を操作する必要があります。また、パンの動作などのタッチ ジェスチャーをサポートしない乗用車もあります。CarPlay プログラミングガイドを読み、ノブとタッチ パッドのイベントによるパンのジェスチャーの詳細をご覧ください。

次に、[Build](ビルド) をクリックしてシミュレーターを構築および実行します。シミュレーターが起動したら、シミュレーターのアプリ メニューを開き、[I/O] - > [External Displays](外部ディスプレイ) - > [CarPlay] を選択します。2 つ目のウィンドウが開き、CarPlay のホーム画面にアプリが表示されます。

結果として得られるアプリは次のようになります。

Screenshot: Showing the HERE Map running on a CarPlay and iPhone simulator.

CarPlayサンプルアプリを試す

完成した「HelloMapCarPlay」サンプルアプリはGitHubで確認できます。

次のステップ

このチュートリアルでは、既存のアプリに CarPlay のサポートを追加する方法を説明します。2つの個別のMapViewインスタンス (デバイスで1つとヘッドユニットで1つ) を実行しますが、両方のMapViewインスタンスは同じアプリのライフサイクル内で実行されるため、デバイスとヘッドユニット間で機能を引き継ぐことができます。たとえば、ユーザーは自宅のモバイル機器でルート計画を開始し、その後、車載のUSBにそのデバイスを接続して運転を開始できます (アプリがNavigateを使用している場合)。ナビゲーションの進行状況が車のヘッドユニットに表示され、デバイスには運転操作の詳細などのサポート情報が表示されます。

情報を増やしすぎてドライバーの注意をそらさないようにします。では、音声操作を追加で実装して、アプリを操作できるようにしてはどうでしょうか。他にもアイデアをいくつか示します。

  • 速度警告アシスタントを追加します。これにより、現在有効な制限速度がヘッドのユニットディスプレイに表示され、この制限速度を超えると音声で警告されます。
  • 現在走行している道路の道路属性情報をサポートとして表示します。
  • ガソリンスタンド、レストラン、観光スポットなど、近くのPOI (施設情報) の場所情報を表示するアプリを実装します。

EN 日本語

HERE documentation

Find answers to your product and technical questions

Documentation

What's new

Videos

EN 日本語

HERE ドキュメント

製品や技術に関する質問の答えを見つけましょう。より多くの内容と最新の情報については、英語版をご覧ください。

ドキュメント

ダイナミックマップ

動的コンテンツ関連のAPIをアプリやサービスに活用して、ドライバーが安全・快適かつ予定どおりに目的地へ到着できるよう支援します。

地図とデータ

世界中を走行する多数のマッピング車両から得られる最新の位置情報データを活用し、精度の高い地図やカスタムレイヤーを構築できます。

最新情報

動画

(function () { const input = document.querySelector('input[data-typeahead]'); if (!input) return; // Prevent the form from submitting/navigating input.closest('form')?.addEventListener('submit', e => e.preventDefault()); input.addEventListener('input', function () { const q = this.value.trim().toLowerCase(); document.querySelectorAll('.nav-group-name').forEach(group => { let anyVisible = false; group.querySelectorAll('.nav-group-task').forEach(task => { const text = task.textContent.trim().toLowerCase(); const show = !q || text.includes(q); task.style.display = show ? '' : 'none'; if (show) anyVisible = true; }); // Hide the whole group header if nothing matches group.style.display = anyVisible || !q ? '' : 'none'; }); }); })(); (function () { function onTokenClick(event) { var link = event.target.closest('.sdk-for-ios .item .token'); if (!link) return; event.preventDefault(); console.log('token clicked', link.textContent.trim()); var item = link.closest('.item'); if (!item) return; var content = item.querySelector('.height-container'); if (!content) { console.log('no .height-container found for item', item); return; } var isHidden = window.getComputedStyle(content).display === 'none'; content.style.display = isHidden ? 'block' : 'none'; link.classList.toggle('token-open', isHidden); var href = link.getAttribute('href'); if (href) { if (history.pushState) history.pushState({}, '', href); else location.hash = href; } } function openHashTarget() { var hash = window.location.hash.slice(1); if (!hash) return; var anchor = document.querySelector('.sdk-for-ios a[name="' + hash + '"]'); if (!anchor) return; var item = anchor.closest('.item'); if (!item) return; var link = item.querySelector('.token'); var content = item.querySelector('.height-container'); if (!link || !content) return; content.style.display = 'block'; link.classList.add('token-open'); } function init() { console.log('HERE SDK accordion init'); openHashTarget(); } document.removeEventListener('click', onTokenClick); document.addEventListener('click', onTokenClick); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } window.addEventListener('hashchange', openHashTarget); window.addEventListener('pageLoad', init); })();