【Swift UI】MapKitで地図機能を実装する方法!iOS17以降
この記事からわかること
- Swift UIで地図(Maps)を表示する方法
- MapKitフレームワークの使い方
- アノテーションの設置方法とカスタマイズ
index
[open]
\ アプリをリリースしました /
環境
- Xcode:16.3
- iOS:18.5
- Swift:6
- macOS:Sequoia 15.4
MapKitがiOS17以降で使い方が変わった
アプリ上で簡単に地図Viewを実装できるMapKitですがiOS17以降からMap構造体のイニシャライザなどが一部非推奨に変わり、別のイニシャライザを使用して実装する方針に変更になりました。以前はcoordinateRegionにMKCoordinateRegion型を渡すことで表示位置などを制御していましたが、少し使い方自体が変わっているので新しい実装方法をまとめて行きます。
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()
}
}
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つを許可
指定の位置で地図を表示させる
指定の初期位置で地図を表示させるにはinitialPositionにMapCameraPosition型で位置情報を指定します。例えば初期表示位置を東京スカイツリーの場所にしたい場合は以下のように実装します。
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()
}
}
表示範囲を制御する
マップの表示範囲を制御するにはboundsにMapCameraBounds型で指定します。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をスライドさせた時に限界表示領域)はcenterCoordinateBoundsにMKCoordinateRegion型で指定し、中心地と許可する範囲をMKCoordinateSpan型で指定します。例えば「東京スカイツリーから±0.05度範囲までしか表示させないようにする」場合は以下のようになります。
private let bounds = MapCameraBounds(
centerCoordinateBounds: MKCoordinateRegion(
center: defaultCenter,
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
)
)
マーカーやアノテーションを配置する
地図にマーカーやアノテーションを配置するにはcontentにクロージャーの中に指定します。この中にはMarkerやAnnotation型などいろいろ配置することができるようになっています。
マーカーを表示する
シンプルなデフォルトのマーカーを表示させたいだけであればMarkerを使用します。引数に表示名と表示位置を指定するだけでピンク色のマーカーが表示されます。
Map(initialPosition: .region(defaultRegion)) {
// 標準ピン
Marker("東京スカイツリー", coordinate: ContentView.defaultCenter)
}.ignoresSafeArea()
カスタムアノテーションを表示する
カスタムデザインのアノテーションを表示させたい場合はAnnotationを使用します。クロージャーの中にViewで自由にデザインを実装することが可能です。
Map(initialPosition: .region(defaultRegion)) {
// 標準ピン
Annotation("東京スカイツリー", coordinate: ContentView.defaultCenter) {
VStack {
Image(systemName: "building")
Text("スカイツリー")
}
.padding(5)
.background(Color.white)
.cornerRadius(8)
}
}.ignoresSafeArea()
領域を可視化する
領域を可視化させたい場合はMapCircleを使用します。radiusには円の半径を指定します。
Map(initialPosition: .region(defaultRegion)) {
MapCircle(center: ContentView.defaultCenter, radius: 500)
.foregroundStyle(.blue.opacity(0.2))
}.ignoresSafeArea()
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だけでは完結できなかった地図上をタップした位置の緯度・経度を取得する実装が簡単にできるようになりました。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()
}
現在位置を表示する
地図上にユーザーの現在位置を表示するにはUserAnnotation型を使用します。ただユーザー位置情報を表示するためにはユーザーの許可が必要になるので注意してください。許可申請方法などは以下の記事を参考にしてください。
【SwiftUI】現在地を取得して表示する方法!MapKitで地図アプリ
UserAnnotationを使用することで現在位置にアノテーションを付与することができますが地図自体の初期表示位置が変更されるわけではないので注意してください。mapControlsにMapUserLocationButtonを付与することで簡単に現在位置へ地図を動かすことも可能です。
Map(initialPosition: .region(viewModel.region)) {
UserAnnotation(anchor: .center) { userLocation in
Image(systemName: "figure.wave")
}
}.mapControls {
// 現在位置に戻るボタン
MapUserLocationButton()
}
おすすめ記事
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。







