SwiftUI(Swift)を使って iOS アプリに地図を追加する

1. 始める前に

この Codelab では、SwiftUI で Map SDK for iOS を利用する方法を解説します。

screenshot-iphone-12-black@2x.png

前提条件

  • Swift の基本知識
  • SwiftUI の基本的な扱いへの習熟

演習内容

  • Maps SDK for iOS を有効化して使用し、SwiftUI を使って iOS アプリに Google マップを追加します。
  • 地図にマーカーを追加します。
  • SwiftUI のビューと GMSMapView オブジェクトの間で、相互に状態の受け渡しを行います。

必要なもの

2. 準備

以下の有効化の手順では、Maps 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 キーが必要です。

3. スターター コードをダウンロードする

できるだけ早く演習を開始できるように、この Codelab で使用できるスターター コードが用意されています。すぐに次のステップに進んでも問題ありませんが、ご自身で構築するためのすべての手順を確認したい場合は、最後までお読みください。

  1. git がインストールされている場合は、リポジトリのクローンを作成します。
git clone https://github.com/googlecodelabs/maps-ios-swiftui.git

あるいは、以下のボタンをクリックしてソースコードをダウンロードすることもできます。

  1. コードを入手したら、cdstarter/GoogleMapsSwiftUI ディレクトリに移動します。
  2. carthage update --platform iOS を実行して、Maps SDK for iOS をダウンロードします。
  3. 最後に、Xcode で GoogleMapsSwiftUI.xcodeproj ファイルを開きます。

4. コードの概要

ダウンロードしたスターター プロジェクトには次のクラスが含まれ、あらかじめ実装されています。

  • AppDelegate - アプリケーションの UIApplicationDelegate。ここで Maps SDK for iOS の初期化が行われます。
  • City - ひとつの都市を表す構造体(都市の名称と座標を含む)。
  • MapViewController - Google マップ(GMSMapView)を含むシンプルな UIKit UIViewController
  • SceneDelegate - ContentView のインスタンス化が行われる、アプリケーションの UIWindowSceneDelegate

これに加えて以下のクラスが部分的に実装されており、この Codelab 内で実装を完成させることになります。

  • ContentView - アプリを格納する最上位の SwiftUI ビュー。
  • MapViewControllerBridge - UIKit ビューを SwiftUI ビューにブリッジするクラス。具体的には、SwiftUI 内で MapViewController へのアクセスを実現するクラスです。

5. SwiftUI と UIKit の違い

SwiftUI は、iOS アプリケーションの開発において UIKit を代替する UI フレームワークとして、iOS 13 で導入されました。先代の UIKit と比べると、SwiftUI にはさまざまな利点があります。たとえば次のようなものが挙げられます。

  • 状態が変化するとビューが自動的に更新されます。State と呼ばれるオブジェクトにより、含まれる値が変化すると UI が自動的に更新される仕組みです。
  • ライブ プレビューによる迅速な開発が可能です。ライブ プレビューにより、Swift UI ビューのプレビューを Xcode で手軽に参照できるため、視覚的な変化を確認するためにコードをビルドしてエミュレータにデプロイする作業は最小限で済むようになっています。
  • Source of Truth(信頼できる情報源)は Swift 内にあります。SwiftUI のビューはすべて Swift 内で宣言されるため、従来のように Interface Builder を使う必要はありません。
  • UIKit との相互運用性が確保されています。UIKit との相互運用性により、既存のアプリは既存のビューをそのまま活かしつつ、SwiftUI を追加で組み込んでいくことができます。また、Maps SDK for iOS のように SwiftUI に未対応のライブラリも、SwiftUI で使用することができます。

デメリットもいくつかあります。

  • SwiftUI は iOS 13 以降でなければ利用できません。
  • ビュー階層を Xcode のプレビューで検証することができません。

SwiftUI の状態とデータフロー

SwiftUI では、宣言型アプローチによる新しい形の UI 作成が可能です。ビューの外観とさまざまな状態を SwiftUI に伝えると、あとはシステムが自動的に処理してくれます。もとになっている状態がイベントやユーザー行動により変化すると、SwiftUI が自動的にビューを更新します。この設計は一般に「単方向データフロー」と呼ばれます。本 Codelab ではこの設計の詳細には踏み込みませんが、Apple のドキュメント State and Data Flow を読んで理解を深めることをおすすめします。

UIViewRepresentable または UIViewControllerRepresentable を使った UIKit と SwiftUI のブリッジ

Maps SDK for iOS は UIKit を基盤として設計されており、SwiftUI 対応のビューはまだ提供できません。このため、SwiftUI 内で使用する場合は、UIViewRepresentable または UIViewControllerRepresentable への準拠が必要となります。これらのプロトコルはそれぞれ、UIKit で構築された UIViewUIViewController を SwiftUI が組み込むことを可能にします。SwiftUI のビューに Google マップを追加すること自体はどちらのプロトコルでも可能ですが、次のステップでは、UIViewControllerRepresentable を使って地図を含む UIViewController を組み込む方法を解説します。

6 地図を追加する

このセクションでは、SwiftUI のビューに Google マップを追加します。

add-a-map-screenshot@2x.png

API キーを追加する

アプリに表示する地図をアカウントと関連付けるため、ここまでのステップで作成した API キーを、Maps SDK for iOS に提供する必要があります。

API キーを提供するには、AppDelegate.swift ファイルを開いて application(_, didFinishLaunchingWithOptions) メソッドを見つけます。現在 SDK は、GMSServices.provideAPIKey() を介して、「YOUR_API_KEY」という文字列で初期化されています。この文字列を実際の API キーで置き換えましょう。この手順を完了すると、アプリケーション起動時に Maps SDK for iOS が初期化されます。

MapViewControllerBridge を使って Google マップを追加する

API キーを SDK に提供できたら、次はアプリに地図を表示しましょう。

スターター コードで提供されているビュー コントローラ MapViewController は現在、ビューの中に GMSMapView を含みます。ただ、これは UIKit で作成されたビュー コントローラなので、ContentView 内で使用できるよう、このクラスを SwiftUI にブリッジする必要があります。手順は次のとおりです。

  1. ファイル MapViewControllerBridge を Xcode で開きます。

このクラスは、UIKit の UIViewController を SwiftUI のビューとして使用できるようラッピングするために必要なプロトコルである UIViewControllerRepresentable に準拠します。逆に言えば、このプロトコルに準拠すると、UIKit のビューを SwiftUI のビューにブリッジすることが可能です。プロトコルに準拠するためには、次の 2 つのメソッドを実装する必要があります。

  • makeUIViewController(context) - もとになる UIViewController を作成するために SwiftUI が呼び出すメソッドです。UIViewController をインスタンス化して初期状態を渡す処理をこのメソッドで行います。
  • updateUIViewController(_, context) - 状態が変化するたびに SwiftUI が呼び出すメソッドです。もとになっている UIViewController に状態の変化に応じて変更を加える処理を、このメソッドで行います。
  1. MapViewController を作成します。

関数 makeUIViewController(context) 内で、新しい MapViewController をインスタンス化し、結果として返します。以上が済むと、MapViewControllerBridge は次のようになります。

MapViewControllerBridge

import GoogleMaps
import SwiftUI

struct MapViewControllerBridge: UIViewControllerRepresentable {

  func makeUIViewController(context: Context) -> MapViewController {
    return MapViewController()
  }

  func updateUIViewController(_ uiViewController: MapViewController, context: Context) {
  }
}

ContentView で MapViewControllerBridge を使用する

MapViewControllerBridgeMapViewController のインスタンスを作成するようになったので、次はこの構造体を ContentView 内で使うことにより地図を表示します。

  1. ファイル ContentView を Xcode で開きます。

SceneDelegate でインスタンス化された ContentView に、最上位のアプリケーション ビューが格納されています。地図の追加はこのファイル内から行います。

  1. body プロパティ内に MapViewControllerBridge を作成します。

このファイルの body プロパティ内には、あらかじめ ZStack が用意され、実装されています。ZStack には現在、操作可能かつドラッグ可能な都市のリストが含まれています。このリストは後の手順で使用します。ここでは ZStack 内で、ZStack の最初の子ビューとして MapViewControllerBridge を作成し、都市のリストのビューの後ろに地図が表示されるようにします。これにより、ContentView 内の body プロパティの内容は次のようになります。

ContentView

var body: some View {

  let scrollViewHeight: CGFloat = 80

  GeometryReader { geometry in
    ZStack(alignment: .top) {
      // Map
      MapViewControllerBridge()

      // Cities List
      CitiesList(markers: $markers) { (marker) in
        guard self.selectedMarker != marker else { return }
        self.selectedMarker = marker
        self.zoomInCenter = false
        self.expandList = false
      }  handleAction: {
        self.expandList.toggle()
      } // ...
    }
  }
}
  1. アプリを実行してみましょう。デバイスの画面に地図が読み込まれ、画面下方にはドラッグ可能な都市のリストが表示されているはずです。

7. 地図にマーカーを追加する

前のステップでは、操作可能な都市のリストとともに地図が表示されるようにしました。このセクションでは、リスト内の各都市に対応するマーカーを追加します。

map-with-markers@2x.png

マーカーを状態(State)として扱う

ContentView は現在 markers というプロパティを宣言しており、これは静的プロパティ cities で宣言されている各都市に対応する GMSMarker のリストです。このプロパティには SwiftUI プロパティ ラッパー State のアノテーションが付いており、SwiftUI の管理対象に指定されている点に注意しましょう。このため、このプロパティ内で変化(マーカーの追加や削除など)が検出されると、この状態を使用しているビューも更新されます。

ContentView

  static let cities = [
    City(name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: 37.7576, longitude: -122.4194)),
    City(name: "Seattle", coordinate: CLLocationCoordinate2D(latitude: 47.6131742, longitude: -122.4824903)),
    City(name: "Singapore", coordinate: CLLocationCoordinate2D(latitude: 1.3440852, longitude: 103.6836164)),
    City(name: "Sydney", coordinate: CLLocationCoordinate2D(latitude: -33.8473552, longitude: 150.6511076)),
    City(name: "Tokyo", coordinate: CLLocationCoordinate2D(latitude: 35.6684411, longitude: 139.6004407))
  ]

  /// State for markers displayed on the map for each city in `cities`
  @State var markers: [GMSMarker] = cities.map {
    let marker = GMSMarker(position: $0.coordinate)
    marker.title = $0.name
    return marker
  }

ContentViewmarkers プロパティを CitiesList クラスに渡すことにより都市のリストを表示している点に注目しましょう。

CitiesList

struct CitiesList: View {

  @Binding var markers: [GMSMarker]

  var body: some View {
    GeometryReader { geometry in
      VStack(spacing: 0) {
        // ...
        // List of Cities
        List {
          ForEach(0..<self.markers.count) { id in
            let marker = self.markers[id]
            Button(action: {
              buttonAction(marker)
            }) {
              Text(marker.title ?? "")
            }
          }
        }.frame(maxWidth: .infinity)
      }
    }
  }
}

Binding を介して State を MapViewControllerBridge に渡す

markers プロパティのデータを表示している都市のリストに加えて、このプロパティを MapViewControllerBridge 構造体に渡して、地図上にマーカーが表示されるようにします。手順は次のとおりです。

  1. MapViewControllerBridge 内で、@Binding アノテーションの付いた新しい markers プロパティを宣言する

MapViewControllerBridge

struct MapViewControllerBridge: : UIViewControllerRepresentable {
  @Binding var markers: [GMSMarker]
  // ...
}
  1. MapViewControllerBridge で、markers プロパティを利用するように updateUIViewController(_, context) メソッドを更新する

前のステップで触れたとおり、状態に変化があるたびに、SwiftUI によって updateUIViewController(_, context) が呼び出されます。markers 内のマーカーが表示されるよう地図を更新する処理は、このメソッド内で行います。そのためには、各マーカーの map プロパティを更新する必要があります。この手順を完了すると、MapViewControllerBridge は次のようになります。

import GoogleMaps
import SwiftUI

struct MapViewControllerBridge: UIViewControllerRepresentable {

  @Binding var markers: [GMSMarker]

  func makeUIViewController(context: Context) -> MapViewController {
    return MapViewController()
  }

  func updateUIViewController(_ uiViewController: MapViewController, context: Context) {
    // Update the map for each marker
    markers.forEach { $0.map = uiViewController.map }
  }
}
  1. markers プロパティを ContentView から MapViewControllerBridge に渡す

MapViewControllerBridge に新しいプロパティを追加したため、このプロパティの値を MapViewControllerBridge のイニシャライザで受け渡すことが必要になっています。このため、この時点でアプリをビルドしようとしてもコンパイルできません。これを修正するには、MapViewControllerBridge が作成される ContentView を更新して、markers プロパティを渡すようにします。

struct ContentView: View {
  // ...
  var body: some View {
    // ...
    GeometryReader { geometry in
      ZStack(alignment: .top) {
        // Map
        MapViewControllerBridge(markers: $markers)
        // ...
      }
    }
  }
}

MapViewControllerBridge が受け取るのはバインド済みのプロパティなので、markers を渡す際に接頭辞 $ が使用されていることに注目しましょう。$ は Swift プロパティ ラッパー用の予約済み接頭辞で、State に適用した場合、Binding が返されます。

  1. アプリを実行して、地図にマーカーが表示されることを確認しましょう。

8. 選択した都市への移動をアニメーション表示する

前のステップでは、SwiftUI のビュー間で State を受け渡すことにより、地図にマーカーを追加しました。このステップでは、操作可能なリストでタップされた都市 / マーカーへの移動を、アニメーション付きで表示します。アニメーション表示を行うには、State の変化に対応して地図のカメラ位置を変更します。地図のカメラの概念について詳しくは、Camera and View を参照してください。

animate-city@2x.png

選択した都市までアニメーション付きで地図を移動させる

選択した都市までアニメーション付きで地図を移動させる方法は次のとおりです。

  1. MapViewControllerBridge で新しい Binding を定義する

ContentView には、selectedMarker という State プロパティがあります。このプロパティは初期値が nil で、リストから都市が選択されるたびに更新されます。この処理は、ContentView 内の CitiesList ビューの buttonAction が担当しています。

ContentView

CitiesList(markers: $markers) { (marker) in
  guard self.selectedMarker != marker else { return }
  self.selectedMarker = marker
  // ...
}

選択したマーカーまでアニメーション付きで地図を移動できるよう、MapViewControllerBridgeselectedMarker が変化するたびにそれを状態の変化として検出できる必要があります。よって、MapViewControllerBridge 内で GMSMarker 型の新しい Binding を定義し、プロパティを selectedMarker と名付けます。

MapViewControllerBridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
  @Binding var selectedMarker: GMSMarker?
}
  1. selectedMarker が変化するたびに地図のアニメーション表示を行うよう MapViewControllerBridge を更新する

新しい Binding を宣言したら、選択したマーカーまでアニメーション付きで地図が移動するよう、MapViewControllerBridgeupdateUIViewController_, context) 関数を更新する必要があります。下のコードをコピーして使用しましょう。

struct MapViewControllerBridge: UIViewControllerRepresentable {
  @Binding var selectedMarker: GMSMarker?

  func updateUIViewController(_ uiViewController: MapViewController, context: Context) {
    markers.forEach { $0.map = uiViewController.map }
    selectedMarker?.map = uiViewController.map
    animateToSelectedMarker(viewController: uiViewController)
  }

  private func animateToSelectedMarker(viewController: MapViewController) {
    guard let selectedMarker = selectedMarker else {
      return
    }

    let map = viewController.map
    if map.selectedMarker != selectedMarker {
      map.selectedMarker = selectedMarker
      DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        map.animate(toZoom: kGMSMinZoomLevel)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
          map.animate(with: GMSCameraUpdate.setTarget(selectedMarker.position))
          DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
            map.animate(toZoom: 12)
          })
        }
      }
    }
  }
}

animateToSelectedMarker(viewController) 関数は、GMSMapViewanimate(with) 関数を使って、地図アニメーションのシーケンスを実行します。

  1. ContentViewselectedMarkerMapViewControllerBridge に渡す

MapViewControllerBridge で新しい Binding の宣言が済んだら、ContentView を更新して、MapViewControllerBridge のインスタンス化の際に selectedMarker を渡すようにします。

ContentView

struct ContentView: View {
  // ...
  var body: some View {
    // ...
    GeometryReader { geometry in
      ZStack(alignment: .top) {
        // Map
        MapViewControllerBridge(markers: $markers, selectedMarker: $selectedMarker)
        // ...
      }
    }
  }
}

このステップが完了すると、リスト内で新たな都市を選択するたびに、地図がアニメーション表示されるようになります。

SwiftUI のビューをアニメーション表示して都市を強調する

State 遷移の際のアニメーション処理は SwiftUI が自動的に行うため、ビューをアニメーション表示させるのは簡単です。これを確認するため、地図移動のアニメーションが完了した後、選択した都市にビューをフォーカスさせるアニメーションをさらに追加してみましょう。手順は次のとおりです。

  1. onAnimationEnded クロージャを MapViewControllerBridge に追加する

SwiftUI によるアニメーションは、前の手順で追加した地図のアニメーション表示の後で行われるため、MapViewControllerBridge 内で onAnimationEnded という名の新しいクロージャを宣言し、animateToSelectedMarker(viewController) メソッド内の最後の地図アニメーションから 0.5 秒の遅延後にクロージャを呼び出します。

MapViewControllerBridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
    var onAnimationEnded: () -> ()

    private func animateToSelectedMarker(viewController: MapViewController) {
    guard let selectedMarker = selectedMarker else {
      return
    }

    let map = viewController.map
    if map.selectedMarker != selectedMarker {
      map.selectedMarker = selectedMarker
      DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        map.animate(toZoom: kGMSMinZoomLevel)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
          map.animate(with: GMSCameraUpdate.setTarget(selectedMarker.position))
          DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
            map.animate(toZoom: 12)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
              // Invoke onAnimationEnded() once the animation sequence completes
              onAnimationEnded()
            })
          })
        }
      }
    }
  }
}
  1. MapViewControllerBridgeonAnimationEnded を実装する

ContentView 内の MapViewControllerBridge がインスタンス化される箇所に、onAnimationEnded クロージャを実装します。下のコードをコピー&ペーストしましょう。zoomInCenter という名の新しい State を追加するとともに、clipShape を使ってビューを変更し、zoomInCenter の値に応じて切り取り図形の直径を変化させる内容になっています。

ContentView

struct ContentView: View {
  @State var zoomInCenter: Bool = false
  // ...
  var body: some View {
    // ...
    GeometryReader { geometry in
      ZStack(alignment: .top) {
        // Map
        let diameter = zoomInCenter ? geometry.size.width : (geometry.size.height * 2)
        MapViewControllerBridge(markers: $markers, selectedMarker: $selectedMarker, onAnimationEnded: {
          self.zoomInCenter = true
        })
        .clipShape(
           Circle()
             .size(
               width: diameter,
               height: diameter
             )
             .offset(
               CGPoint(
                 x: (geometry.size.width - diameter) / 2,
                 y: (geometry.size.height - diameter) / 2
               )
             )
        )
        .animation(.easeIn)
        .background(Color(red: 254.0/255.0, green: 1, blue: 220.0/255.0))
      }
    }
  }
}
  1. アプリを実行して、アニメーションを確認してみましょう。

9. SwiftUI にイベントを送信する

このステップでは、GMSMapView から発信されるイベントをリッスンし、そのイベントを SwiftUI に送ります。具体的には、地図ビューにデリゲートを設定し、カメラ移動イベントをリッスンすることによって、ある都市がフォーカスされ地図カメラがジェスチャーによって移動した後、地図を見渡しやすいように地図ビューのフォーカスを解除します。

SwiftUI の Coordinator を使用する

GMSMapView は、カメラ位置の変更やマーカーのタップといったイベントを発信します。こういったイベントをリッスンする仕組みは、GMSMapViewDelegate プロトコルによるものです。SwiftUI では、UIKit ビュー コントローラのデリゲートとして使用する Coordinator という概念が新たに導入されています。よって SwiftUI の世界では、GMSMapViewDelegate プロトコルへの準拠は Coordinator に担当させます。その方法は次のとおりです。

  1. MapViewControllerBridge 内に MapViewCoordinator という名の Coordinator を作成する

MapViewControllerBridge クラス内に入れ子でクラスを作成し、MapViewCoordinator と名付けます。このクラスは、GMSMapViewDelegate に準拠するとともに、MapViewControllerBridge をプロパティとして宣言する必要があります。

MapViewControllerBridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
  // ...
  final class MapViewCoordinator: NSObject, GMSMapViewDelegate {
    var mapViewControllerBridge: MapViewControllerBridge

    init(_ mapViewControllerBridge: MapViewControllerBridge) {
      self.mapViewControllerBridge = mapViewControllerBridge
    }
  }
}
  1. MapViewControllerBridgemakeCoordinator() を実装する

次に、MapViewControllerBridge 内に makeCoordinator() メソッドを実装し、前のステップで作成した MapViewCoodinator のインスタンスを返します。

MapViewControllerBridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
  // ...
  func makeCoordinator() -> MapViewCoordinator {
    return MapViewCoordinator(self)
  }
}
  1. MapViewCoordinator を地図ビューのデリゲートに設定する

カスタム コーディネータが作成できたので、次はそのコーディネータを、ビュー コントローラの地図ビューのデリゲートに設定します。そのためには、makeUIViewController(context) でビュー コントローラの初期化を更新します。前のステップで作成したコーディネータには、Context オブジェクトからアクセスできます。

MapViewControllerBridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
  // ...
  func makeUIViewController(context: Context) -> MapViewController {
    let uiViewController = MapViewController()
    uiViewController.map.delegate = context.coordinator
    return uiViewController
  }
  1. カメラ移動イベントを上方プロパゲートできるよう、MapViewControllerBridge にクロージャを追加する

目的はビューを更新してカメラの移動を反映することなので、ブール値を受け付ける新しいクロージャ プロパティを MapViewControllerBridge 内に mapViewWillMove という名前で宣言し、MapViewCoordinator 内のデリゲート メソッド mapView(_, willMove) でこのクロージャを呼び出します。SwiftUI のビューがジェスチャー関連のカメラ移動イベントだけに反応できるよう、gesture の値をクロージャに渡します。

MapViewControllerBridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
  var mapViewWillMove: (Bool) -> ()
  //...

  final class MapViewCoordinator: NSObject, GMSMapViewDelegate {
    // ...
    func mapView(_ mapView: GMSMapView, willMove gesture: Bool) {
      self.mapViewControllerBridge.mapViewWillMove(gesture)
    }
  }
}
  1. mapWillMove の値を渡すよう ContentView を更新する

MapViewControllerBridge で新しいクロージャを宣言したので、ContentView を更新して新しいクロージャの値を渡します。クロージャ内で、移動イベントがジェスチャーに関係するものである場合は、State zoomInCenterfalse に切り替えます。これにより、地図がジェスチャーによって移動した場合に、地図を全体表示に戻すことができます。

ContentView

struct ContentView: View {
  @State var zoomInCenter: Bool = false
  // ...
  var body: some View {
    // ...
    GeometryReader { geometry in
      ZStack(alignment: .top) {
        // Map
        let diameter = zoomInCenter ? geometry.size.width : (geometry.size.height * 2)
        MapViewControllerBridge(markers: $markers, selectedMarker: $selectedMarker, onAnimationEnded: {
          self.zoomInCenter = true
        }, mapViewWillMove: { (isGesture) in
          guard isGesture else { return }
          self.zoomInCenter = false
        })
        // ...
      }
    }
  }
}
  1. アプリを実行して、新しい変更点を確認してみましょう。

10. 完了

これで、この Codelab は完了です!今回の幅広い学習内容をもとに、ぜひご自身でも Maps SDK for iOS を使った SwiftUI アプリの開発に挑戦してみてください。

学習した内容

  • SwiftUI と UIKit の違い
  • UIViewControllerRepresentable を使って SwiftUI と UIKit をブリッジする方法
  • StateBinding を使って地図ビューに変更を加える方法
  • Coordinator を使って地図ビューから SwiftUI にイベントを送信する方法

次のステップ

  • Maps SDK for iOS - Maps SDK for iOS の公式ドキュメント
  • Places SDK for iOS - 現在地周辺のローカル ビジネスやスポットを発見
  • maps-sdk-for-ios-samples - Maps SDK for iOS のあらゆる機能を実演したサンプルコード(GitHub 内)
  • SwiftUI - SwiftUI に関する Apple の公式ドキュメント
  • 皆様のお役に立つコンテンツをご提供できるよう、以下のアンケートにご協力ください。

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

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

ご希望の Codelab が上記にない場合、こちらからリクエストしてください