見出し画像

SwiftUI Mapを使って一瞬で地図とアノテーションを表示させる

3連休でマトリックス3部作を久々に見切ったションローです。
1作目の公開が1999年だったようなのですが、当時は自分がプログラマーになるとは思っていなかったしちょっと遅刻したくらいで怒られる会社・仕事は嫌だなぁなんて思っていたのを思い出しました。

最近の趣味iOSアプリ開発は基本的にSwiftUIベースでやっているのですが、ある開発で地図表示が必要になったのでその辺りを調べてみました。

SwiftUIでMapを表示する

今回の完全なコードはこちら

SwiftUIを用いて、地図の表示アノテーションの表示アノテーションをタップできるようにするまで実装しています。

地図の表示

最低限地図表示に必要なのはMapMKCoordinateRegionです。

struct MapView: View {
   @State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 37.33501993272811, longitude: -122.00912876460102),
                                            latitudinalMeters: 30000, longitudinalMeters: 30000)
   
   var body: some View {
       Map(coordinateRegion: $region)
           .edgesIgnoringSafeArea(.all)
   }
}

regionBinding<MKCoordinateRegion>になっているのは、ユーザのMapへの操作結果を受け取る必要があるからですね。
これをLive Previewを見ると以下のようになります。

スクリーンショット 2021-01-11 17.21.42

ちなみに .edgesIgnoringSafeArea(.all) はMapをSafeArea内にも表示するために付与しています。

アノテーションを表示する

次はアノテーション表示です。
まずはアノテーションのデータソースになる構造体(今回はAnnotationという名前にしました)を作ります。

struct Annotation: Identifiable {
   let id = UUID()
   let name: String
   let coordinate: CLLocationCoordinate2D
}

この構造体はIdentifiableに準拠している必要があります。
次にこの構造体のリストを作り、Mapに渡します。

struct MapView: View {
   @State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 37.33501993272811, longitude: -122.00912876460102),
                                            latitudinalMeters: 30000, longitudinalMeters: 30000)
   private var annotations: [Annotation] = [
       Annotation(name: "Apple Park",
           coordinate: CLLocationCoordinate2D(latitude: 37.33501993272811,
                                              longitude: -122.00912876460102)),
       Annotation(name: "Googleplex",
           coordinate: CLLocationCoordinate2D(latitude: 37.42231267779884,
                                              longitude: -122.08400387147682)),
       Annotation(name: "Facebook HQ",
           coordinate: CLLocationCoordinate2D(latitude:37.48493581456912,
                                              longitude: -122.14960893718997)),
   ]
   
   var body: some View {
       Map(coordinateRegion: $region,
           annotationItems: annotations,
           annotationContent: { (annotation) in
               MapAnnotation(coordinate: annotation.coordinate) {
                   VStack {
                       Text(annotation.name)
                       Image(systemName: "mappin.circle.fill")
                           .foregroundColor(.blue)
                   }
               }
           })
           .edgesIgnoringSafeArea(.all)
   }
}

Mapの引数annotationItemsにAnnotation構造体のリストを渡すのと、annotationContentに「Map上に配置されるアノテーション用のView」を返すクロージャを渡します。

上記のアノテーション用ViewはMapAnnotationProtocolに準拠している必要があり、デフォルトではMapAnnotation<Content>MapMarkerMapPinが用意されています。

アノテーションの様子は以下です。

スクリーンショット 2021-01-11 17.38.29

若干分かりづらいですが、青い画像 + テキストが3箇所表示されています。

アノテーションをタップできるようにする

最後にタップですが、まずはタップ(選択)されたことをAnnotationに保持できるようにします。

struct Annotation: Identifiable {
   let id = UUID()
   let name: String
   let coordinate: CLLocationCoordinate2D
   var selected: Bool = false // <- New!!
}

次にAnnotationのリストに@Stateを付与し、値を変更可能にします。

    @State private var annotations: [Annotation] = [
    ...省略...

最後に、MapAnnotationにジェスチャを追加します。
直接は追加できないので、MapAnnotationが返すViewに付与しましょう。

                MapAnnotation(coordinate: annotations.coordinate) {
                   VStack {
                       Text(annotation.name)
                       Image(systemName: "mappin.circle.fill")
                           .foregroundColor( annotation.selected ? .red : .blue)
                           .onTapGesture {
                               if let idx = annotations.firstIndex(where: { $0.id == annotation.id }) {
                                   annotations[idx].selected.toggle()
                               }
                           }
                   }
               }

今回は未選択時(青)と選択時(赤)で色を変えるように実装しました。

スクリーンショット 2021-01-11 17.49.22

Live Previewですが、タップ等のインタラクションもできるのマジクソ便利で鼻血が出そうになりますね。🥰

まとめ

SwiftUIでMapを使うとMKMapView + UIViewRepresentableを利用することなく簡単に地図とアノテーション表示、アノテーションとのインタラクションができることを学びました。

ただ、どうもconvertPoint:toCoordinateFromView:に相当するメソッドが存在しないようなので「地図をタップして新規にアノテーションを追加する」を実現する場合はMKMapView + UIViewRepresentableを使う必要があるかもしれません。
もしMapだけで上記を実現する方法を知っている人がいれば教えてもらえると幸いです。

また今回気づいたんですが、noteはgistsを記事内に直接埋め込めるんですね。
フルのコードをgistsに上げてたりするので便利!使っていこう。