【Swift UI】MapKitで地図機能を実装する方法!iOS17以降

【Swift UI】MapKitで地図機能を実装する方法!iOS17以降

この記事からわかること

  • Swift UI地図(Maps)を表示する方法
  • MapKitフレームワーク使い方
  • アノテーション設置方法とカスタマイズ

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

MapKitがiOS17以降で使い方が変わった

アプリ上で簡単に地図Viewを実装できるMapKitですがiOS17以降からMap構造体のイニシャライザなどが一部非推奨に変わり、別のイニシャライザを使用して実装する方針に変更になりました。以前はcoordinateRegionMKCoordinateRegion型を渡すことで表示位置などを制御していましたが、少し使い方自体が変わっているので新しい実装方法をまとめて行きます。


struct ContentView: View {

  @State  var region = MKCoordinateRegion(
      center : CLLocationCoordinate2D(
          latitude: 35.710057714926265,  // 緯度
          longitude: 139.81071829999996 // 経度
      ),
      latitudinalMeters: 1000.0, // 南北
      longitudinalMeters: 1000.0 // 東西
  )

  var body: some View {
      // 地図を表示
      Map(coordinateRegion: $region)
          .edgesIgnoringSafeArea(.bottom)
  }
}

iOS17以前の実装方法などは以下の記事を参考にしてください。

地図を表示させる

地図を表示させるだけであれば1行で完結します。この場合は特に表示位置を指定していないのでデフォルトの位置が表示されます。日本から開いているからなのか日本が表示されました。

struct ContentView: View {
    var body: some View {
        Map()
        .ignoresSafeArea()
    }
}
【Swift UI】MapKitで地図機能を実装する方法!iOS17以降

Map構造体を使うことは変わっていませんがイニシャライザがいろいろ変わっているのでみていきます。

新しいイニシャライザ

公式リファレンス:Map

iOS17以降で推奨されているMap構造体のイニシャライザは以下のような引数を持つものに更新されました。ざっくり説明を入れると以下のような感じになります。

init(
  /// マップの表示範囲を制御
  bounds: MapCameraBounds? = nil,
  /// ユーザーがマップ上でできる操作の種類(パン・ズーム・回転など)を制御
  interactionModes: MapInteractionModes = .all,
  /// マップのアニメーションや状態を他のビューと同期するために使うスコープ識別子
  scope: Namespace.ID? = nil,
  /// マップ上に配置するピンやマーカーなどのコンテンツを定義するクロージャ
  @MapContentBuilder content: () -> C
)

=> 定義:init(bounds:interactionModes:scope:content:)

init<C>(
  /// 初期表示位置
  initialPosition: MapCameraPosition,
  bounds: MapCameraBounds? = nil, 
  interactionModes: MapInteractionModes = .all, 
  scope: Namespace.ID? = nil, 
  @MapContentBuilder content: () -> C
)

=> 定義:init(initialPosition:bounds:interactionModes:scope:content:)

interactionModes

ユーザーが操作できる機能を指定値から設定

.pan  : スワイプ(ドラッグ)操作を許可
.zoom : 拡大・縮小の操作(ダブルタッチ or ピンチ操作)を許可
.all  : 上記2つを許可

指定の位置で地図を表示させる

指定の初期位置で地図を表示させるにはinitialPositionMapCameraPosition型で位置情報を指定します。例えば初期表示位置を東京スカイツリーの場所にしたい場合は以下のように実装します。

struct ContentView: View {
    
    /// 初期表示位置:東京スカイツリーの場所
    static private let defaultCenter = CLLocationCoordinate2D(
        latitude: 35.709152712026265,
        longitude: 139.80771829999996
    )
    /// 表示メーター
    static private let latitudinalMeters: Double = 1000.0
    static private let longitudinalMeters: Double = 1000.0
    
    private let defaultRegion: MKCoordinateRegion = .init(
        center: defaultCenter,
        latitudinalMeters: latitudinalMeters,
        longitudinalMeters: longitudinalMeters
    )
    
    var body: some View {
        Map(initialPosition: .region(defaultRegion))
            .ignoresSafeArea()
    }
}

表示範囲を制御する

マップの表示範囲を制御するにはboundsMapCameraBounds型で指定します。MapCameraBounds型ではズーム可能な距離や表示領域などを制御することが可能です。例えばズーム可能な距離を制御する場合はminimumDistance/maximumDistanceにメートル単位で値を指定します。

private let bounds = MapCameraBounds(
    // 最小ズーム距離(1km = 1000m)
    minimumDistance: 1000,
    // 最大ズーム距離(5km = 5000m)
    maximumDistance: 5000
)

var body: some View {
    Map(
        initialPosition: .region(defaultRegion),
        bounds: bounds
    ).ignoresSafeArea()
}

パンできる範囲(Mapをスライドさせた時に限界表示領域)centerCoordinateBoundsMKCoordinateRegion型で指定し、中心地と許可する範囲をMKCoordinateSpan型で指定します。例えば「東京スカイツリーから±0.05度範囲までしか表示させないようにする」場合は以下のようになります。

    private let bounds = MapCameraBounds(
    centerCoordinateBounds: MKCoordinateRegion(
        center: defaultCenter,
        span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
    )
)

マーカーやアノテーションを配置する

地図にマーカーやアノテーションを配置するにはcontentにクロージャーの中に指定します。この中にはMarkerAnnotation型などいろいろ配置することができるようになっています。

マーカーを表示する

シンプルなデフォルトのマーカーを表示させたいだけであればMarkerを使用します。引数に表示名と表示位置を指定するだけでピンク色のマーカーが表示されます。

Map(initialPosition: .region(defaultRegion)) {
    // 標準ピン
    Marker("東京スカイツリー", coordinate: ContentView.defaultCenter)
}.ignoresSafeArea()
【Swift UI】MapKitで地図機能を実装する方法!iOS17以降

カスタムアノテーションを表示する

カスタムデザインのアノテーションを表示させたい場合はAnnotationを使用します。クロージャーの中にViewで自由にデザインを実装することが可能です。

Map(initialPosition: .region(defaultRegion)) {
    // 標準ピン
    Annotation("東京スカイツリー", coordinate: ContentView.defaultCenter) {
        VStack {
            Image(systemName: "building")
            Text("スカイツリー")
        }
        .padding(5)
        .background(Color.white)
        .cornerRadius(8)
    }
}.ignoresSafeArea()
【Swift UI】MapKitで地図機能を実装する方法!iOS17以降

領域を可視化する

領域を可視化させたい場合はMapCircleを使用します。radiusには円の半径を指定します。

Map(initialPosition: .region(defaultRegion)) {
    MapCircle(center: ContentView.defaultCenter, radius: 500)
        .foregroundStyle(.blue.opacity(0.2))
}.ignoresSafeArea()
【Swift UI】MapKitで地図機能を実装する方法!iOS17以降

2つのポイント間を直線で結ぶ

2つのポイント間を直線で結ばせたい場合はMapPolylineを使用します。coordinates結びたい位置情報を指定します。ただこれはあくまで直線で結ぶだけで経路表示ではないので注意してください。

【SwiftUI】MapKitで地図に経路を表示させる方法!MKMapViewDelegate


private let route = [
        CLLocationCoordinate2D(latitude: 35.7091527, longitude: 139.8077183),
        CLLocationCoordinate2D(latitude: 35.7100, longitude: 139.8000)
    ]

Map(initialPosition: .region(defaultRegion)) {
    MapPolyline(coordinates: route)
        .stroke(.red, lineWidth: 3)
}.ignoresSafeArea()
【Swift UI】MapKitで地図機能を実装する方法!iOS17以降

地図上をタップした位置の緯度・経度を取得する

個人的に一番嬉しかったのがこれです。以前はSwift UIだけでは完結できなかった地図上をタップした位置の緯度・経度を取得する実装が簡単にできるようになりました。MapReaderでラップしてonTapGestureからタップした位置情報を取得することができます。

@State private var coordinate: CLLocationCoordinate2D = defaultCenter
var body: some View {
    ZStack {
        
        VStack {
            Text("緯度: \(coordinate.latitude)")
            Text("経度: \(coordinate.longitude)")
        }.background(Material.ultraThinMaterial)
            .zIndex(1)
        
        MapReader { proxy in
            Map(initialPosition: .region(defaultRegion))
                .onTapGesture { position in
                    guard let selectedCoordinate = proxy.convert(position, from: .local) else { return }
                    coordinate = selectedCoordinate
                }
        }.zIndex(0)
    }.ignoresSafeArea()
}
【Swift UI】MapKitで地図機能を実装する方法!iOS17以降

現在位置を表示する

地図上にユーザーの現在位置を表示するにはUserAnnotation型を使用します。ただユーザー位置情報を表示するためにはユーザーの許可が必要になるので注意してください。許可申請方法などは以下の記事を参考にしてください。

【SwiftUI】現在地を取得して表示する方法!MapKitで地図アプリ

UserAnnotationを使用することで現在位置にアノテーションを付与することができますが地図自体の初期表示位置が変更されるわけではないので注意してください。mapControlsMapUserLocationButtonを付与することで簡単に現在位置へ地図を動かすことも可能です。

Map(initialPosition: .region(viewModel.region)) {
    UserAnnotation(anchor: .center) { userLocation in
        Image(systemName: "figure.wave")
    }
}.mapControls {
    // 現在位置に戻るボタン
    MapUserLocationButton()
}

おすすめ記事

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

Search Box

Sponsor

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article