SwiftUI で最初の 3D 地図を作成する

1. 始める前に

この Codelab では、Maps 3D SDK for iOS を使用して SwiftUI で 3D 地図アプリを作成する方法について説明します。

サンフランシスコの 3D 地図を表示しているアプリ

学習内容:

  • カメラを操作して場所を表示したり、地図上を移動したりする方法。
  • マーカーとモデルを追加する方法
  • ラインとポリゴンを描画する方法
  • ユーザーがプレイス マーカーをクリックしたときに処理する方法。

前提条件

  • 課金が有効になっている Google コンソール プロジェクト
  • API キー(必要に応じて Maps 3D SDK for iOS に制限可能)。
  • SwiftUI を使用した iOS 開発に関する基本的な知識。

演習内容

  • Xcode を設定し、Swift Package Manager を使用して SDK を導入する
  • API キーを使用するようにアプリを設定する
  • 基本的な 3D 地図をアプリに追加する
  • カメラを操作して特定の場所を飛行する
  • 地図にマーカー、ライン、ポリゴン、モデルを追加する

必要なもの

  • Xcode 15 以降。

2. セットアップする

次の有効化の手順では、Maps 3D SDK for iOS を有効にする必要があります。

Google Maps Platform を設定する

課金を有効にした Google Cloud Platform アカウントとプロジェクトをまだ作成していない場合は、Google Maps Platform スタートガイドに沿って請求先アカウントとプロジェクトを作成してください。

  1. Cloud Console で、プロジェクトのプルダウン メニューをクリックし、この Codelab に使用するプロジェクトを選択します。

  1. Google Cloud Marketplace で、この Codelab に必要な Google Maps Platform API と SDK を有効にします。詳しい手順については、こちらの動画またはドキュメントをご覧ください。
  2. Cloud Console の [認証情報] ページで API キーを生成します。詳しい手順については、こちらの動画またはドキュメントをご覧ください。Google Maps Platform へのすべてのリクエストで API キーが必要になります。

Maps 3D SDK for iOS を有効にする

Maps 3D SDK for iOS は、コンソールの [Google Maps Platform] > [API とサービス] メニューリンクから確認できます。

[有効にする] をクリックして、選択したプロジェクトで API を有効にします。

Google コンソールで Maps 3D SDK を有効にする

3. 基本的な SwiftUI アプリを作成する

注: 各ステップの解答コードは、GitHub の Codelab サンプルアプリ リポジトリで確認できます。

Xcode で新しいアプリを作成します。

このステップのコードは、GitHub の GoogleMaps3DDemo フォルダにあります。

Xcode を開いて新しいアプリを作成し、SwiftUI を指定します。

アプリを GoogleMaps3DDemo とし、パッケージ名を com.example.GoogleMaps3DDemo にします。

GoogleMaps3D ライブラリをプロジェクトにインポートする

Swift Package Manager を使用して SDK をプロジェクトに追加します。

Xcode プロジェクトまたはワークスペースで、[ファイル] > [パッケージの依存関係を追加] に移動します。URL として https://github.com/googlemaps/ios-maps-3d-sdk を入力し、Enter キーを押してパッケージを取得し、[パッケージを追加] をクリックします。

[パッケージ プロダクトの選択] ウィンドウで、指定したメイン ターゲットに GoogleMaps3D が追加されることを確認します。完了したら、[パッケージを追加] をクリックします。

インストールを確認するには、成果目標の [全般] ペインに移動します。[フレームワーク]、[ライブラリ]、[埋め込みコンテンツ] に、インストールされているパッケージが表示されます。Project Navigator の [Package Dependencies] セクションでパッケージとそのバージョンを確認することもできます。

API キーを追加する

API キーをアプリにハードコードすることもできますが、これはおすすめしません。構成ファイルを追加すると、API キーをシークレットとして保持し、ソース管理にチェックインする必要がなくなります。

プロジェクトのルートフォルダに新しい構成ファイルを作成する

Xcode で、プロジェクト エクスプローラ ウィンドウが表示されていることを確認します。プロジェクトのルートを右クリックし、[テンプレートから新規ファイル] を選択します。[Configuration Settings File] が表示されるまでスクロールします。これを選択して [次へ] をクリックします。ファイルに Config.xcconfig という名前を付け、プロジェクトのルートフォルダが選択されていることを確認します。[作成] をクリックしてファイルを作成します。

エディタで、構成ファイルに次の行を追加します。MAPS_API_KEY = YOUR_API_KEY

YOUR_API_KEY を API キーに置き換えます。

この設定を Info.plist に追加します。

これを行うには、プロジェクトのルートを選択して [情報] タブをクリックします。

値が $(MAPS_API_KEY)MAPS_API_KEY という新しいプロパティを追加します。

サンプルアプリのコードには、このプロパティを指定する Info.plist ファイルがあります。

地図を追加する

GoogleMaps3DDemoApp.swift というファイルを開きます。これはアプリのエントリ ポイントとメイン ナビゲーションです。

ContentView() を呼び出して、Hello World メッセージを表示します。

エディタで ContentView.swift を開きます。

GoogleMaps3Dimport ステートメントを追加します。

var body: some View {} コードブロック内のコードを削除します。body 内に新しい Map() を宣言します。

Map を初期化するために必要な最小構成は MapMode です。使用可能な値は次の 2 つです。

  • .hybrid - 道路とラベルが付いた衛星画像
  • .satellite - 衛星画像のみ。

ぜひ.hybridにご用命ください。

ContentView.swift ファイルは次のようになります。

import GoogleMaps3D
import SwiftUI

@main
struct ContentView: View {
    var body: some View {
      Map(mode: .hybrid)
    }
}

API キーを設定します。

API キーは、地図の初期化前に設定する必要があります。

これを行うには、地図を含む Viewinit() イベント ハンドラで Map.apiKey を設定します。ContentView() を呼び出す前に GoogleMaps3DDemoApp.swift で設定することもできます。

GoogleMaps3DDemoApp.swift で、WindowGrouponAppear イベント ハンドラに Map.apiKey を設定します。

構成ファイルから API キーを取得する

Bundle.main.infoDictionary を使用して、構成ファイルで作成した MAPS_API_KEY 設定にアクセスします。

import GoogleMaps3D
import SwiftUI

@main
struct GoogleMaps3DDemoApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
    .onAppear {
      guard let infoDictionary: [String: Any] = Bundle.main.infoDictionary else {
        fatalError("Info.plist not found")
      }
      guard let apiKey: String = infoDictionary["MAPS_API_KEY"] as? String else {
        fatalError("MAPS_API_KEY not set in Info.plist")
      }
      Map.apiKey = apiKey
    }
  }
}

アプリをビルドして実行し、正しく読み込まれることを確認します。地球の地図が表示されます。

地球を示す 3D 地図

4. カメラを使用して地図ビューを操作する

カメラの状態オブジェクトを作成する

3D マップビューは Camera クラスによって制御されます。このステップでは、位置、高度、方位、傾斜、ロール、範囲を指定して地図ビューをカスタマイズする方法を学習します。

サンフランシスコの 3D 地図ビュー

カメラの設定を保存する Helpers クラスを作成する

MapHelpers.swift という名前の新しい空のファイルを追加します。新しいファイルで GoogleMaps3D をインポートし、Camera クラスに拡張機能を追加します。sanFrancisco という変数を追加します。この変数を新しい Camera オブジェクトとして初期化します。カメラを latitude: 37.39, longitude: -122.08 に配置します。

import GoogleMaps3D

extension Camera {
 public static var sanFrancisco: Camera = .init(latitude: 37.39, longitude: -122.08)
}

アプリに新しいビューを追加する

CameraDemo.swift という名前の新しいファイルを作成します。新しい SwiftUI ビューの基本的なアウトラインをファイルに追加します。

Camera 型の camera という @State 変数を追加します。先ほど定義した sanFrancisco カメラに初期化します。

@State を使用すると、地図をカメラの状態にバインドし、信頼できる情報源として使用できます。

@State var camera: Camera = .sanFrancisco

camera プロパティを含めるように Map() 関数呼び出しを変更します。カメラの状態バインディング $camera を使用して、camera プロパティを Camera @State オブジェクト(.sanFrancisco)に初期化します。

import SwiftUI
import GoogleMaps3D

struct CameraDemo: View {
  @State var camera: Camera = .sanFrancisco
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid)
    }
  }
}

基本的なナビゲーション UI をアプリに追加する

アプリのメイン エントリ ポイント GoogleMaps3DDemoApp.swiftNavigationView を追加します。

これにより、ユーザーはデモのリストを表示し、各デモをクリックして開くことができます。

GoogleMaps3DDemoApp.swift を編集して、新しい NavigationView を追加します。

2 つの NavigationLink 宣言を含む List を追加します。

最初の NavigationLink は、Text の説明 Basic MapContentView() を開きます。

2 番目の NavigationLinkCameraDemo() が開きます。

...
      NavigationView {
        List {
          NavigationLink(destination: ContentView()) {
            Text("Basic Map")
          }
          NavigationLink(destination: CameraDemo()) {
            Text("Camera Demo")
          }
        }
      }
...

Xcode プレビューを追加する

プレビューは、アプリの変更を加える際にアプリを表示して操作できる強力な Xcode 機能です。

プレビューを追加するには、CameraDemo.swift を開きます。struct の外側に #Preview {} コードブロックを追加します。

#Preview {
 CameraDemo()
}

Xcode でプレビュー パネルを開くか、更新します。地図にサンフランシスコが表示されます。

カスタム 3D ビューを設定する

追加のパラメータを指定してカメラを制御できます。

  • heading: カメラを向ける北からの方位(度数)。
  • tilt: 傾斜角度(度)。0 は真上、90 は水平を向きます。
  • roll: カメラの垂直面を中心としたロール角(度)
  • range: 緯度と経度の位置からのカメラの距離(メートル単位)
  • altitude: カメラの海抜

これらの追加パラメータを指定しない場合、デフォルト値が使用されます。

カメラビューに 3D データを多く表示するには、近づいて傾斜したビューを表示するように初期パラメータを設定します。

MapHelpers.swift で定義した Camera を編集して、altitudeheadingtiltrollrange の値を含めます。

public static var sanFrancisco: Camera = .init(
  latitude: 37.7845812,
  longitude: -122.3660241,
  altitude: 585,
  heading: 288.0,
  tilt: 75.0,
  roll: 0.0,
  range: 100)

アプリをビルドして実行し、新しい 3D ビューを表示して探索します。

5. 基本的なカメラ アニメーション

ここまでは、カメラを使用して、傾斜、高度、方位、範囲を指定した単一の位置を指定してきました。このステップでは、これらのプロパティを初期状態から新しい状態にアニメーション化することで、カメラビューを移動する方法を学習します。

シアトルの 3D 地図

場所に移動する

Map.flyCameraTo() メソッドを使用して、カメラを最初の位置から新しい位置にアニメーション化します。

flyCameraTo() メソッドは、次のパラメータを取ります。

  • 終点を表す Camera
  • duration: アニメーションの実行時間(秒)。
  • trigger: 状態が変化したときにアニメーションをトリガーするオブザーバブル オブジェクト。
  • completion: アニメーションの完了時に実行されるコード。

目的地を定義する

MapHelpers.swift ファイルを開きます。

シアトルを表示する新しいカメラ オブジェクトを定義します。

public static var seattle: Camera = .init(latitude:
47.6210296,longitude: -122.3496903, heading: 149.0, tilt: 77.0, roll: 0.0, range: 4000)

アニメーションをトリガーするボタンを追加します。

CameraDemo.swift を開きます。struct 内に新しいブール変数を宣言します。

animate という名前にして、初期値を false にします。

@State private var animate: Bool = false

VStack の下に Button を追加します。Button は地図アニメーションを開始します。

Button に適切な Text(「飛行を開始」など)を指定します。

import SwiftUI
import GoogleMaps3D

struct CameraDemo: View {
  @State var camera:Camera = .sanFrancisco
  @State private var animate: Bool = false

  var body: some View {
    VStack{
      Map(camera: $camera, mode: .hybrid)
      Button("Start Flying") {
      }
    }
  }
}

Button クロージャーに、animate 変数の状態を切り替えるコードを追加します。

      Button("Start Flying") {
        animate.toggle()
      }

アニメーションを開始します。

animate 変数の状態が変化したときに flyCameraTo() アニメーションをトリガーするコードを追加します。

  var body: some View {
    VStack{
      Map(camera: $camera, mode: .hybrid)
        .flyCameraTo(
          .seattle,
          duration: 5,
          trigger: animate,
          completion: {  }
        )
      Button("Start Flying") {
        animate.toggle()
      }
    }
  }

特定の場所を飛行する

場所を飛行するには、Map.flyCameraAround() メソッドを使用します。このメソッドは、次のパラメータを使用します。

  • ロケーションとビューを定義する Camera
  • duration(秒単位)
  • rounds: アニメーションを繰り返す回数。
  • trigger: アニメーションをトリガーするオブザーバブル オブジェクト。
  • callback: アニメーションの実行時に実行されるコード。

flyAround という新しい @State 変数を定義し、初期値を false に設定します。

その後、flyCameraTo() メソッド呼び出しの直後に flyCameraAround() の呼び出しを追加します。

ビューがスムーズに変化するように、飛行時間は比較的長くする必要があります。

flyCameraTo() が完了したら、トリガー オブジェクトの状態を変更して、flyCameraAround() アニメーションをトリガーしてください。

コードの内容は次のようになります。

import SwiftUI
import GoogleMaps3D

struct CameraDemo: View {
  @State var camera:Camera = .sanFrancisco
  @State private var animate: Bool = false
  @State private var flyAround: Bool = false

  var body: some View {
    VStack{
      Map(camera: $camera, mode: .hybrid)
        .flyCameraTo(
          .seattle,
          duration: 5,
          trigger: animate,
          completion: { flyAround = true }
        )
        .flyCameraAround(
          .seattle,
          duration: 15,
          rounds: 0.5,
          trigger: flyAround,
          callback: {  }
        )
      Button("Start Flying") {
        animate.toggle()
      }
    }
  }
}

#Preview {
  CameraDemo()
}

アプリをプレビューするか実行して、flyCameraTo() アニメーションが完了するとカメラが目的地を周回することを確認します。

6. 地図にマーカーを追加します。

このステップでは、地図上にマーカーピンを描画する方法を学習します。

Marker オブジェクトを作成して地図に追加します。SDK は、マーカーにデフォルトのアイコンを使用します。最後に、マーカーの高さやその他のプロパティを調整して、表示方法を変更します。

地図マーカーが表示された 3D 地図

マーカーのデモ用に新しい SwiftUI ビューを作成します。

プロジェクトに新しい Swift ファイルを追加します。MarkerDemo.swift という名前を付けます。

SwiftUI ビューのアウトラインを追加し、CameraDemo で行ったように地図を初期化します。

import SwiftUI
import GoogleMaps3D

struct MarkerDemo: View {
  @State var camera: Camera = .sanFrancisco
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid)
    }
  }
}

Marker オブジェクトを初期化する

mapMarker という新しいマーカー変数を宣言します。MarkerDemo.swiftstruct コードブロックの上部。

定義は、camera 宣言の下の行に配置します。このサンプルコードでは、使用可能なすべてのプロパティを初期化します。

  @State var mapMarker: Marker = .init(
    position: .init(
      latitude: 37.8044862,
      longitude: -122.4301493,
      altitude: 0.0),
    altitudeMode: .absolute,
    collisionBehavior: .required,
    extruded: false,
    drawsWhenOccluded: true,
    sizePreserved: true,
    zIndex: 0,
    label: "Test"
  )

地図にマーカーを追加します。

マーカーを描画するには、地図の作成時に呼び出されるクロージャーにマーカーを追加します。

struct MarkerDemo: View {
  @State var camera: Camera = .sanFrancisco
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {
        mapMarker
      }
    }
  }
}

GoogleMaps3DDemoApp.swift に新しい NavigationLink を追加し、リンク先を MarkerDemo()Text を「Marker Demo」に設定します。

...
      NavigationView {
        List {
          NavigationLink(destination: Map()) {
            Text("Basic Map")
          }
          NavigationLink(destination: CameraDemo()) {
            Text("Camera Demo")
          }
          NavigationLink(destination: MarkerDemo()) {
            Text("Marker Demo")
          }
        }
      }
...

アプリをプレビューして実行する

プレビューを更新するか、アプリを実行してマーカーを確認します。

押し出したマーカー

マーカーは、altitudealtitudeMode を使用して地面または 3D メッシュの上に配置できます。

MarkerDemo.swiftmapMarker 宣言を、extrudedMarker という新しい Marker 変数にコピーします。

altitude にゼロ以外の値を設定します。50 で十分です。

altitudeMode.relativeToMesh に変更し、extrudedtrue に設定します。次のコード スニペットの latitudelongitude を使用して、超高層ビルの上にマーカーを配置します。

  @State var extrudedMarker: Marker = .init(
    position: .init(
      latitude: 37.78980534,
      longitude:  -122.3969349,
      altitude: 50.0),
    altitudeMode: .relativeToMesh,
    collisionBehavior: .required,
    extruded: true,
    drawsWhenOccluded: true,
    sizePreserved: true,
    zIndex: 0,
    label: "Extruded"
  )

アプリをもう一度実行するか、プレビューします。マーカーは 3D 建物の上に表示されます。

7. 地図にモデルを追加します。

Model は、Marker と同じ方法で追加できます。URL でアクセスできるモデルファイル、またはプロジェクトにローカル ファイルとして追加できるモデルファイルが必要です。このステップでは、この Codelab の GitHub リポジトリからダウンロードできるローカル ファイルを使用します。

熱気球のモデルが配置されたサンフランシスコの 3D 地図

モデルファイルをプロジェクトに追加する

Xcode プロジェクトに Models という名前の新しいフォルダを作成します。

GitHub のサンプルアプリ リポジトリからモデルをダウンロードします。Xcode プロジェクト ビューの新しいフォルダにドラッグして、プロジェクトに追加します。

ターゲットをアプリの主なターゲットに設定してください。

プロジェクトの [Build Phases] > [Copy Bundle Resources] の設定を確認します。モデルファイルは、バンドルにコピーされたリソースのリストに含まれている必要があります。ない場合は、[+] をクリックして追加します。

モデルをアプリに追加します。

ModelDemo.swift という名前の新しい SwiftUI ファイルを作成します。

前の手順と同様に、SwiftUIGoogleMaps3Dimport ステートメントを追加します。

bodyVStack 内に Map を宣言します。

import SwiftUI
import GoogleMaps3D

struct ModelDemo: View {
  @State var camera: Camera = .sanFrancisco
  
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {
        
      }
    }
  }
}

バンドルからモデルパスを取得します。このコードは struct の外側に追加します。

private let fileUrl = Bundle.main.url(forResource: "balloon", withExtension: "glb")

構造体内にモデルの変数を宣言します。

fileUrl が指定されていない場合に備えて、デフォルト値を指定します。

  @State var balloonModel: Model = .init(
    position: .init(
      latitude: 37.791376,
      longitude: -122.397571,
      altitude: 300.0),
    url: URL(fileURLWithPath: fileUrl?.relativePath ?? ""),
    altitudeMode: .absolute,
    scale: .init(x: 5, y: 5, z: 5),
    orientation: .init(heading: 0, tilt: 0, roll: 0)
  )

3. 地図でモデルを使用します。

Marker の追加と同様に、Map 宣言で Model への参照を指定します。

  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {
        balloonModel
      }
    }
  }

アプリをプレビューして実行する

GoogleMaps3DDemoApp.swift に新しい NavigationLink を追加します。宛先は ModelDemo()Text は「Model Demo」です。

...
          NavigationLink(destination: ModelDemo()) {
            Text("Model Demo")
          }
...

プレビューを更新するか、アプリを実行してモデルを表示します。

8. 地図上にラインとポリゴンを描画します。

このステップでは、3D 地図にラインとポリゴンのシェイプを追加する方法を学びます。

わかりやすくするために、シェイプを LatLngAltitude オブジェクトの配列として定義します。実際のアプリケーションでは、データはファイル、API 呼び出し、データベースから読み込まれます。

2 つのポリゴンと 1 つのポリラインが表示されたサンフランシスコの 3D 地図

シェイプ データを管理するシェイプ オブジェクトを作成します。

サンフランシスコのダウンタウンを対象とする新しい Camera 定義を MapHelpers.swift に追加します。

  public static var downtownSanFrancisco: Camera = .init(latitude: 37.7905, longitude: -122.3989, heading: 25, tilt: 71, range: 2500) 

プロジェクトに ShapesDemo.swift という新しいファイルを追加します。View プロトコルを実装する ShapesDemo という struct を追加し、body を追加します。

struct ShapesDemo: View {
  @State var camera: Camera = .downtownSanFrancisco

  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {

      }
    }
  }
}

シェイプデータを管理するために使用するクラスは PolylinePolygon です。ShapesDemo.swift を開き、次のように struct に追加します。

var polyline: Polyline = .init(coordinates: [
    LatLngAltitude(latitude: 37.80515638571346, longitude: -122.4032569467164, altitude: 0),
    LatLngAltitude(latitude: 37.80337073509504, longitude: -122.4012878349353, altitude: 0),
    LatLngAltitude(latitude: 37.79925208843463, longitude: -122.3976697250461, altitude: 0),
    LatLngAltitude(latitude: 37.7989102378512, longitude: -122.3983408725656, altitude: 0),
    LatLngAltitude(latitude: 37.79887832784348, longitude: -122.3987094864192, altitude: 0),
    LatLngAltitude(latitude: 37.79786443410338, longitude: -122.4066878788802, altitude: 0),
    LatLngAltitude(latitude: 37.79549248916587, longitude: -122.4032992702785, altitude: 0),
    LatLngAltitude(latitude: 37.78861484290265, longitude: -122.4019489189814, altitude: 0),
    LatLngAltitude(latitude: 37.78618687561075, longitude: -122.398969592545, altitude: 0),
    LatLngAltitude(latitude: 37.7892310309145, longitude: -122.3951458683092, altitude: 0),
    LatLngAltitude(latitude: 37.7916358762409, longitude: -122.3981969390652, altitude: 0)
  ])
  .stroke(GoogleMaps3D.Polyline.StrokeStyle(
    strokeColor: UIColor(red: 0.09803921568627451, green: 0.403921568627451, blue: 0.8235294117647058, alpha: 1),
    strokeWidth: 10.0,
    outerColor: .white,
    outerWidth: 0.2
    ))
  .contour(GoogleMaps3D.Polyline.ContourStyle(isGeodesic: true))

  var originPolygon: Polygon = .init(outerCoordinates: [
    LatLngAltitude(latitude: 37.79165766856578, longitude:  -122.3983762901255, altitude: 300),
    LatLngAltitude(latitude: 37.7915324439261, longitude:  -122.3982171091383, altitude: 300),
    LatLngAltitude(latitude: 37.79166617650914, longitude:  -122.3980478493319, altitude: 300),
    LatLngAltitude(latitude: 37.79178986470217, longitude:  -122.3982041104199, altitude: 300),
    LatLngAltitude(latitude: 37.79165766856578, longitude:  -122.3983762901255, altitude: 300 )
  ],
  altitudeMode: .relativeToGround)
  .style(GoogleMaps3D.Polygon.StyleOptions(fillColor:.green, extruded: true) )

  var destinationPolygon: Polygon = .init(outerCoordinates: [
      LatLngAltitude(latitude: 37.80515661739527, longitude:  -122.4034307490334, altitude: 300),
      LatLngAltitude(latitude: 37.80503794515428, longitude:  -122.4032633416024, altitude: 300),
      LatLngAltitude(latitude: 37.80517850164195, longitude:  -122.4031056058006, altitude: 300),
      LatLngAltitude(latitude: 37.80529346901115, longitude:  -122.4032622466595, altitude: 300),
      LatLngAltitude(latitude: 37.80515661739527, longitude:  -122.4034307490334, altitude: 300 )
  ],
  altitudeMode: .relativeToGround)
  .style(GoogleMaps3D.Polygon.StyleOptions(fillColor:.red, extruded: true) )

使用されている初期化パラメータに注意してください。

  • altitudeMode: .relativeToGround は、ポリゴンを地面から特定の高さまで押し出すために使用します。
  • altitudeMode: .clampToGround は、ポリラインが地球の表面の形状に沿うようにするために使用されます。
  • .init() が呼び出された後に styleOptions() へのメソッド呼び出しを連結して、Polygon オブジェクトにスタイルが設定される

地図にシェイプを追加する

前の手順と同様に、シェイプは Map 閉じに直接追加できます。VStack 内に Map を作成します。

...
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {
        polyline
        originPolygon
        destinationPolygon
      }
    }
  }
...

アプリをプレビューして実行する

プレビュー コードを追加し、Xcode のプレビュー ペインでアプリを検査します。

#Preview {
  ShapesDemo()
}

アプリを実行するには、新しいデモビューを開く NavigationLinkGoogleMaps3DDemoApp.swift に追加します。

...
          NavigationLink(destination: ShapesDemo()) {
            Text("Shapes Demo")
          }
...

アプリを実行して、追加したシェイプを確認します。

9. 場所マーカーのタップ イベントを処理する

このステップでは、ユーザーがプレイス マーカーをタップしたときに応答する方法について説明します。

プレイス ID を含むポップアップ ウィンドウが表示された地図

注: 地図上に場所マーカーを表示するには、MapMode.hybrid に設定する必要があります。

タップ処理を行うには、Map.onPlaceTap メソッドを実装する必要があります。

onPlaceTap イベントは、タップされたプレイス マーカーのプレイス ID を取得できる PlaceTapInfo オブジェクトを提供します。

プレイス ID を使用して、Places SDK または Places API で詳細情報を検索できます。

新しい Swift ビューを追加する

PlaceTapDemo.swift という名前の新しい Swift ファイルに次のコードを追加します。

import GoogleMaps3D
import SwiftUI

struct PlaceTapDemo: View {
  @State var camera: Camera = .sanFrancisco
  @State var isPresented = false
  @State var tapInfo: PlaceTapInfo?

  var body: some View {
    Map(camera: $camera, mode: .hybrid)
      .onPlaceTap { tapInfo in
        self.tapInfo = tapInfo
        isPresented.toggle()
      }
      .alert(
        "Place tapped - \(tapInfo?.placeId ?? "nil")",
        isPresented: $isPresented,
        actions: { Button("OK") {} }
      )
  }
}
#Preview {
  PlaceTapDemo()
}

アプリをプレビューして実行する

[プレビュー] ペインを開いてアプリをプレビューします。

アプリを実行するには、GoogleMaps3DDemoApp.swift に新しい NavigationLink を追加します。

...
          NavigationLink(destination: PlaceTapDemo()) {
            Text("Place Tap Demo")
          }
...

10. (省略可)さらに活用する

高度なカメラのアニメーション

飛行シミュレーターやハイキングやランニングの再生など、一部のユースケースでは、位置情報やカメラ状態のシーケンスまたはリストに沿ってスムーズにアニメーション化する必要があります。

このステップでは、ファイルから場所のリストを読み込み、各場所を順番にアニメーション化する方法について説明します。

インスブルックへの進入路の 3D 地図表示

一連の位置情報を含むファイルを読み込みます。

GitHub のサンプルアプリ リポジトリから flightpath.json をダウンロードします。

Xcode プロジェクトに JSON という名前の新しいフォルダを作成します。

flightpath.json を Xcode の JSON フォルダにドラッグします。

ターゲットをアプリのメイン ターゲットに設定します。プロジェクトの [バンドル リソースのコピー] 設定にこのファイルが含まれていることを確認します。

アプリに FlightPathData.swiftFlightDataLoader.swift という 2 つの新しい Swift ファイルを作成します。

次のコードをアプリにコピーします。このコードは、flighpath.json というローカル ファイルを読み取り、JSON としてデコードする構造体とクラスを作成します。

FlightPathData 構造体と FlightPathLocation 構造体は、JSON ファイル内のデータ構造を Swift オブジェクトとして表します。

FlightDataLoader クラスは、ファイルからデータを読み取り、デコードします。ObservableObject プロトコルを実装しているため、アプリでデータの変更を監視できます。

解析されたデータは、公開されたプロパティを介して公開されます。

FlightPaths.swift

import GoogleMaps3D

struct FlightPathData: Decodable {
  let flight: [FlightPathLocation]
}

struct FlightPathLocation: Decodable {
  let timestamp: Int64
  let latitude: Double
  let longitude: Double
  let altitude: Double
  let bearing: Double
  let speed: Double
}

FlightDataLoader.swift

import Foundation

public class FlightDataLoader : ObservableObject {

  @Published var flightPathData: FlightPathData = FlightPathData(flight:[])
  @Published var isLoaded: Bool = false

  public init() {
    load("flightpath.json")
  }
  
  public func load(_ path: String) {
    if let url = Bundle.main.url(forResource: path, withExtension: nil){
      if let data = try? Data(contentsOf: url){
        let jsondecoder = JSONDecoder()
        do{
          let result = try jsondecoder.decode(FlightPathData.self, from: data)
          flightPathData = result
          isLoaded = true
        }
        catch {
          print("Error trying to load or parse the JSON file.")
        }
      }
    }
  }
}

各場所に沿ってカメラをアニメーション化する

一連のステップ間でカメラをアニメーション化するには、KeyframeAnimator を使用します。

KeyframeCubicKeyframe として作成されるため、カメラの状態の変化がスムーズにアニメーション化されます。

flyCameraTo() を使用すると、ビューが各ロケーション間で「バウンス」します。

まず、MapHelpers.swift で「innsbruck」というカメラを宣言します。

public static var innsbruck: Camera = .init(
  latitude: 47.263,
  longitude: 11.3704,
  altitude: 640.08,
  heading: 237,
  tilt: 80.0,
  roll: 0.0,
  range: 200)

次に、FlyAlongRoute.swift という新しいファイルに新しいビューを設定します。

SwiftUIGoogleMaps3D をインポートします。VStack 内に MapButton を追加します。ブール値 animation 変数の状態を切り替えるように Button を設定します。

FlightDataLoaderState オブジェクトを宣言します。このオブジェクトは、初期化時に JSON ファイルを読み込みます。

import GoogleMaps3D
import SwiftUI

struct FlyAlongRoute: View {
  @State private var camera: Camera = .innsbruck
  @State private var flyToDuration: TimeInterval = 5
  @State var animation: Bool = true

  @StateObject var flightData: FlightDataLoader = FlightDataLoader()

  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid)
      Button("Fly Along Route"){
        animation.toggle()
      }
    }
  }
}

キーフレームを作成する

基本的なプロセスは、アニメーション シーケンスの新しいフレームを返す関数を作成することです。新しいフレームごとに、アニメーターがアニメーション化する次のカメラ状態が定義されます。この関数を作成したら、ファイル内の各ロケーションを順番に指定して呼び出します。

FlyAlongRoute 構造体に 2 つの関数を追加します。関数 makeKeyFrame は、カメラの状態を含む CubicKeyframe を返します。関数 makeCamera は、飛行データ シーケンス内のステップを受け取り、そのステップを表す Camera オブジェクトを返します。

func makeKeyFrame(step: FlightPathLocation) -> CubicKeyframe<Camera> {
  return CubicKeyframe(
    makeCamera(step: step),
    duration: flyToDuration
  )
}

func makeCamera(step: FlightPathLocation) -> Camera {
  return .init(
    latitude: step.latitude,
    longitude: step.longitude,
    altitude: step.altitude,
    heading: step.bearing,
    tilt: 75,
    roll: 0,
    range: 200
  )
}

アニメーションをまとめる

Map の初期化後に keyframeAnimator を呼び出して、初期値を設定します。

飛行経路の最初の位置に基づくカメラの初期状態が必要です。

アニメーションは、変数の状態変化に基づいてトリガーする必要があります。

keyframeAnimator のコンテンツはマップである必要があります。

実際のキーフレームのリストは、飛行経路の各位置をループして生成されます。

   VStack {
      Map(camera: $camera, mode: .hybrid)
        .keyframeAnimator(
          initialValue: makeCamera(step: flightData.flightPathData.flight[0]),
          trigger: animation,
          content: { view, value in
            Map(camera: .constant(value), mode: .hybrid)
          },
          keyframes: { _ in
            KeyframeTrack(content: {
              for i in  1...flightData.flightPathData.flight.count-1 {
                makeKeyFrame(step: flightData.flightPathData.flight[i])
              }
            })
          }
        )
   }

アプリをプレビューして実行します。

プレビュー パネルを開いてビューをプレビューします。

FlightPathDemo() をデスティネーションとする新しい NavigationLinkGoogleMaps3DDemoApp.swift に追加し、アプリを実行して試します。

11. 完了

次のアプリケーションが正常に作成されました。

  • 基本的な 3D 地図をアプリに追加します。
  • マーカー、ライン、ポリゴン、モデルを地図に追加します。
  • 地図上を飛行したり、特定の場所を飛行したりするカメラを制御するコードを実装します。

学習した内容

  • Xcode SwiftUI アプリに GoogleMaps3D パッケージを追加する方法。
  • API キーとデフォルト ビューを使用して 3D 地図を初期化する方法。
  • マーカー、3D モデル、ライン、ポリゴンを地図に追加する方法。
  • カメラを制御して別の場所への移動をアニメーション化する方法。
  • プレイス マーカーのクリック イベントを処理する方法。

次のステップ

  • Maps 3D SDK for iOS でできることについて詳しくは、デベロッパー ガイドをご覧ください。
  • 皆様のお役に立つコンテンツをご提供できるよう、以下のアンケートにご協力ください。

他にどのような Codelab をご希望ですか。

地図上のデータの可視化 地図のスタイルのカスタマイズの詳細 地図上に 3D インタラクションを構築する