📍

【SwiftUI】MapKitの便利オプションまとめ

に公開

はじめに

AC音楽ゲーム情報アプリ「リズマップ」を開発するにあたり、MapKitのさまざまなオプションを活用した。本記事では、使用した主なオプションについて、サンプルコードとともに簡潔に紹介する。

MapKitに関する情報はあまり多くなく、実装には多くの時間と労力を要した。今後、MapKitを用いて開発を行う者にとって、本記事が少しでも参考になれば幸いである。

1. Mapの基本表示とカメラポジション制御

@State private var position: MapCameraPosition = .userLocation(fallback: .automatic)

Map(position: $position) {
    UserAnnotation()
}
  • MapCameraPosition は SwiftUI での地図視点を制御する新しい構造体で、.userLocation(fallback:)で現在地を基準に自動調整がされる。純正アプリと同じ挙動。
  • UserAnnotation() を配置することで地図上に現在地を表示できる。

2. Annotation でピンを表示してカスタマイズ

既存のMarker()以外にも地図上にカスタムのピンを表示することができる。(Marker()はピン数が増えると非常に動作が重くなる)

Annotation(location.id, coordinate: location.coordinate) {
    Image(systemName: "mappin")
        .foregroundColor(.white)
        .background(
            Circle()
                .fill(Color.pink)
                .frame(width: selected == location.id ? 40 : 30, height: selected == location.id ? 40 : 30)
                .shadow(color: selected == location.id ? Color.black.opacity(0.5) : .clear, radius: 5)
        )
        .onTapGesture {
            detailsModal = true
            selected = location.id
            incrementAdCount()
        }
}
  • .background で色つきの円を配置して、選択時は大きさや影を追加して強調。
  • .onTapGesture によりタップされたときに詳細情報のモーダルを開く処理を実行。
  • incrementAdCount() で広告表示のロジックも組み込み。

3. onMapCameraChange で地図移動に連動

.onMapCameraChange(frequency: .onEnd) { context in
    currentRegion = context.region
}
  • 地図をパンやズームしたときに表示範囲の変更を検知して、現在の表示範囲(MKCoordinateRegion)を更新するためのトリガー。様々な処理に応用できる。

4. MapKit の組み込みUIコンポーネント

現在地へ戻るボタン

MapUserLocationButton(scope: mapScope)
    .background(Color.white)
    .cornerRadius(.infinity)
    .shadow(radius: 10)
  • ネイティブのマップアプリと同様に、1回タップで現在地に遷移(青ボタンに変化)、2回タップ(現在地の状態でタップ)で端末の向きとリアルタイムで地図の向きが同期するようになる。

方位確認用コンパス

MapCompass(scope: mapScope)
  • 北が上の状態では表示されない。
  • タップすると北が上になるように地図が回転する。

スケール表示

MapScaleView(scope: mapScope)
  • @Namespace var mapScope の下、同じ scope 内で使うことで連携効果が出る。
  • ネイティブの美しいUIパーツを自由に配置できる

5. デフォルトコントロールの非表示

.mapControlVisibility(.hidden)
  • MapKit のデフォルトで含まれるズームボタンやスケールを非表示にし、自前で UI を配置しやすくする。

6. 位置情報利用の許可を取得

.onAppear {
    CLLocationManager().requestWhenInUseAuthorization()
}
  • ユーザーが地図を使う前に位置情報を利用するための権限確認
  • 別途info.plistPrivacy - Location When In Use Usage Descriptionを追加する必要がある。

7. 表示範囲内のピンのみを抽出する

func visibleLocations(from allLocations: [Location], in region: MKCoordinateRegion) -> [Location] {
    let center = CLLocation(latitude: region.center.latitude, longitude: region.center.longitude)
    let latitudeDeltaInMeters = region.span.latitudeDelta * 111_000
    let radius = min(latitudeDeltaInMeters / 1.5, visibleRadius)

    return allLocations.filter { location in
        let point = CLLocation(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
        return point.distance(from: center) <= radius
    }
}
  • 表示中の範囲の大きさを算出して、その範囲に入っているピンだけを抽出。
  • 大量のピンを表示しないことでピンタップ時のレスポンスを改善。

おわりに

MapKitを使えば、簡単にカスタム地図を表示することができる。今後も様々な機能を試していきたい。
実際の挙動についてはこちらのアプリで使用しているのでぜひ確認して頂ければと思う。
また、よりよい実装方法があれば情報を頂ければ幸いである。
https://apps.apple.com/jp/app/リズマップ-音ゲー情報アプリ/id6502974735

Discussion