SwiftUI で Map を表示する手順

SwiftUI で Map を表示するには、iOS13 では こちらのQiita記事 のように UIViewRepresentable を使って カスタムView を作成する必要がありました。

しかし、iOS14 からMapという SwiftUI 用の View が標準で搭載され、実装が大変簡略化されました

今回はこの Map についての使い方の紹介していきたいと思います。

SwiftUI で Map を表示する手順

(1) MapKit のインポート

Map という View で表示できるようにはなりましたが、引数として渡す各種データを定義するにはMapKit ライブラリのインポートは依然として必要です。

import MapKit

これが無いとビルドエラーになりますので先ずはお忘れなきよう。

(2) MKCoordinateRegion

利用頻度の高い以下のイニシャライザーには位置情報となる MKCoordinateRegion の状態変数が必要になります(不要なイニシャライザーもありますが今回はそちらの解説はしません)。

①MKCoordinateRegion(center: CLLocationCoordinate2D, latitudinalMeters: CLLocationDistance, longitudinalMeters: CLLocationDistance)
②MKCoordinateRegion(center: CLLocationCoordinate2D, span: MKCoordinateSpan)

1) center: CLLocationCoordinate2D

表示領域の中心位置緯度経度で指定します。

2) latitudinalMeters: CLLocationDistance

緯度に対しての表示領域をメートルで指定します。例えば 100 を指定した場合、中心位置から西に100m、東に100mが表示領域なります。

3) longitudinalMeters: CLLocationDistance

経度に対しての表示領域をメートルで指定します。例えば 100 を指定した場合、中心位置から北に100m、南に100mが表示領域となります。

4) span: MKCoordinateSpan

緯度・経度それぞれに対する表示領域をそれぞれの縮尺(単位は度)で指定します。1度で約111kmあるそうなので比較的小さい値を指定することが多いと思います。例えば、100m を縮尺で表すと 0.0009 くらいになります。

実際は以下のように宣言します。

@State private var region =
    MKCoordinateRegion(
        center: .init(latitude: 35.710263046992736, longitude: 139.81067894034084),
        latitudinalMeters: 300,
        longitudinalMeters: 300
    )
@State private var region =
    MKCoordinateRegion(
        center: .init(latitude: 35.710263046992736, longitude: 139.81067894034084),
        span: MKCoordinateSpan(latitudeDelta: 0.0009, longitudeDelta: 0.0009)
    )

(3) シンプルに表示してみる

Map には表示領域だけのイニシャライザーと、ピンなどを設置する複雑なイニシャライザーがありますが、先ずは前者のイニシャライザーでシンプルに表示してみましょう。

イニシャライザー
Map(coordinateRegion: Binding<MKCoordinateRegion>)

coordinateRegion: Binding

前述の MKCoordinateRegion の状態変数(@State or @Published)をバインディング指定します。

使用例
struct ContentView: View {
    @State private var region = 
        MKCoordinateRegion(
            center: .init(latitude: 35.710263046992736, longitude: 139.81067894034084),
            latitudinalMeters: 300,
            longitudinalMeters: 300
        )
        
    var body: some View {
        Map(coordinateRegion: $region)
            .edgesIgnoringSafeArea(.all)
    }
}
東京スカイツリーの位置

ちなみに余談ですが、.edgesIgnoringSafeArea(.all) で全画面表示ができます。

(4) 複雑な指定方法

複雑、と言っても引数が多いだけで難しい内容ではありません。先ずは3種類のイニシャライザーを確認してみましょう。

① Map(coordinateRegion: Binding<MKCoordinateRegion>, annotationItems: RandomAccessCollection, annotationContent: (Identifiable) -> MapAnnotationProtocol)
② Map(coordinateRegion: Binding<MKCoordinateRegion>, interactionModes: MapInteractionModes, showsUserLocation: Bool, userTrackingMode: Binding<MapUserTrackingMode>?)
③ Map(coordinateRegion: Binding<MKCoordinateRegion>, interactionModes: MapInteractionModes, showsUserLocation: Bool, userTrackingMode: Binding<MapUserTrackingMode>?, annotationItems: RandomAccessCollection, annotationContent: (Identifiable) -> MapAnnotationProtocol)

1) coordinateRegion: Binding

MKCoordinateRegion の状態変数(@State or @Published)をバインディング指定します。

2) interactionModes: MapInteractionModes

ユーザー操作の許可を以下の3つから設定します。

  • .pan スワイプ(ドラッグ)による操作を許可します。
  • .zoom ダブルタッチ or ピンチ操作による拡大・縮小の操作を許可します。
  • .all .pan.zoom の両方を許可します。

3) showsUserLocation: Bool

trueユーザーの現在位置を表示します。

4) userTrackingMode: Binding?

マップがユーザーの現在位置を追跡させるどうかを状態変数で管理します。

  • .follow ユーザーを追跡します。
  • .none ユーザーの追跡を停止します。
指定例)
@State private var userTrackingMode: MapUserTrackingMode = .follow
Map(..., userTrackingMode: $userTrackingMode, ...)

5) annotationItems: RandomAccessCollection

所謂「ピン」の配列を渡します。ピンは Identifiable に準拠した構造体で定義し、CLLocationCoordinate2D 型の緯度経度を持たせます。

指定例)
struct PinItem: Identifiable {
    let id = UUID()
    let coordinate: CLLocationCoordinate2D
}

Map(...,
    annotationItems: [
        PinItem(coordinate: .init(latitude: 35.710263046992736, longitude: 139.81067894034084)),
        PinItem(coordinate: .init(latitude: 35.700263046992736, longitude: 139.80067894034084))
    ],
    ...
)

6) annotationContent: (Identifiable) -> MapAnnotationProtocol

5) のピンの見た目(View)を以下の2種類のどちらかで表示します。

  • MapMarker
  • MapPin
指定例)
annotationContent: { item in
    MapMarker(coordinate: item.coordinate)
}
または
annotationContent: { item in
    MapPin(coordinate: item.coordinate)
}

(5) 複雑な指定の表示例

最後に引数が多いバージョンの表示例を紹介します。

struct ContentView: View {
    @State private var region = MKCoordinateRegion(center: .init(latitude: 35.710263046992736, longitude: 139.81067894034084), latitudinalMeters: 300, longitudinalMeters: 300)
    @State private var userTrackingMode: MapUserTrackingMode = .follow
    
    struct PinItem: Identifiable {
        let id = UUID()
        let coordinate: CLLocationCoordinate2D
    }
    
    var body: some View {
        Map(coordinateRegion: $region,
            interactionModes: .zoom,
            showsUserLocation: true,
            userTrackingMode: $userTrackingMode,
            annotationItems: [
                PinItem(coordinate: .init(latitude: 35.710263046992736, longitude: 139.81067894034084)),
                PinItem(coordinate: .init(latitude: 35.710063046992736, longitude: 139.81047894034084))
            ],
            annotationContent: { item in
                MapMarker(coordinate: item.coordinate)
            }
        ).edgesIgnoringSafeArea(.all)
    }
}
ピンを2つ挿した例

以上、ご参考になれば幸いです。

【おすすめの関連記事】