mapkit について
MapKitは、MapKitとCoreLocationのモダンな非同期APIで地図・位置情報機能を開発するSwiftUI開発者向けのパターン集です。地図上の注釈、位置情報更新、場所検索、ジオコーディング、経路検索を扱います。
主な機能
- 01地図、注釈、オーバーレイ
- 02非同期の位置情報更新とジオフェンス
- 03場所の入力補完と検索
- 04住所のジオコーディング
- 05移動手段別の経路検索
活用シーン
- 01地図上への場所や経路の表示
- 02近隣スポット検索の構築
- 03ユーザー位置の追跡
- 04ジオフェンス機能の作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill mapkitインストール後、Claude Code で/mapkitを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/mapkit-corelocation-patterns.mdmapkit-patterns.md詳細
MapKit
SwiftUI の MapKit と最新の CoreLocation 非同期 API を使って、iOS 17 以降を対象とするマップベースおよび位置情報対応機能を構築します。ビューには Map と MapContentBuilder を、位置情報のストリーミングには CLLocationUpdate.liveUpdates() を、ジオフェンシングには CLMonitor を使用します。
マップの完全なセットアップ、検索、ルート、Look Around、スナップショット、または iOS 26 の場所 API が必要な場合は、references/mapkit-patterns.md をお読みください。タスクに位置情報の更新ライフサイクル、ジオフェンシング、バックグラウンド位置情報、テスト、プライバシーキーが関係する場合は、references/mapkit-corelocation-patterns.md をお読みください。
目次
- ワークフロー
- SwiftUI マップビュー(iOS 17 以降)
- CoreLocation の最新 API
- ジオコーディング
- 検索
- 道順
- PlaceDescriptor(iOS 26 以降)
- よくある間違い
- レビュー用チェックリスト
- リファレンス
ワークフロー
1. マーカーまたはアノテーション付きのマップを追加する
MapKitをインポートします。- 必要に応じて
MapCameraPositionのバインディングを指定し、Mapビューを作成します。 MapContentBuilderクロージャー内にMarker、Annotation、MapPolyline、MapPolygon、またはMapCircleを追加します。.mapStyle()でマップスタイルを設定します。.mapControls { }でマップコントロールを追加します。selection:バインディングで選択を処理します。
2. ユーザーの位置を追跡する
- Info.plist に
NSLocationWhenInUseUsageDescriptionを追加します。 - iOS 18 以降では、認証を管理するために
CLServiceSessionを作成します。 Task内でCLLocationUpdate.liveUpdates()を反復処理します。- UI を更新する前に、距離または精度に基づいて更新をフィルタリングします。
- 位置情報の追跡が不要になったら、タスクを停止します。
3. 場所を検索する
MKLocalSearchCompleterを設定して、オートコンプリート候補を表示します。- クエリを設定する前に、ユーザー入力をデバウンスします(少なくとも 300ms)。
- 選択された補完候補を
MKLocalSearch.Requestに変換して、完全な検索結果を取得します。 - 結果をマーカーまたはリストで表示します。
4. 道順を取得してルートを表示する
- 出発地と目的地の
MKMapItemを指定してMKDirections.Requestを作成します。 transportType(.automobile、.walking、.transit、.cycling)を設定します。MKDirections.calculate()を await します。MapPolyline(route.polyline)でルートを描画します。
5. 既存のマップ/位置情報コードをレビューする
このファイル末尾のレビュー用チェックリストを確認してください。
SwiftUI マップビュー(iOS 17 以降)
import MapKit
import SwiftUI
struct PlaceMap: View {
@State private var position: MapCameraPosition = .automatic
var body: some View {
Map(position: $position) {
Marker("Apple Park", coordinate: applePark)
Marker("Infinite Loop", systemImage: "building.2",
coordinate: infiniteLoop)
}
.mapStyle(.standard(elevation: .realistic))
.mapControls {
MapUserLocationButton()
MapCompass()
MapScaleView()
}
}
}
マーカーとアノテーション
// Balloon marker -- simplest way to pin a location
Marker("Cafe", systemImage: "cup.and.saucer.fill", coordinate: cafeCoord)
.tint(.brown)
// Annotation -- custom SwiftUI view at a coordinate
Annotation("You", coordinate: userCoord, anchor: .bottom) {
Image(systemName: "figure.wave")
.padding(6)
.background(.blue.gradient, in: .circle)
.foregroundStyle(.white)
}
オーバーレイ:ポリライン、ポリゴン、円
Map {
// Polyline from coordinates
MapPolyline(coordinates: routeCoords)
.stroke(.blue, lineWidth: 4)
// Polygon (area highlight)
MapPolygon(coordinates: parkBoundary)
.foregroundStyle(.green.opacity(0.3))
.stroke(.green, lineWidth: 2)
// Circle (radius around a point)
MapCircle(center: storeCoord, radius: 500)
.foregroundStyle(.red.opacity(0.15))
.stroke(.red, lineWidth: 1)
}
カメラ位置
MapCameraPosition はマップに表示する内容を制御します。ユーザーが操作できるようにバインドし、プログラムからカメラを移動できるようにします。
// Center on a region
@State private var position: MapCameraPosition = .region(
MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.334, longitude: -122.009),
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
)
)
// Follow user location
@State private var position: MapCameraPosition = .userLocation(fallback: .automatic)
// Specific camera angle (3D perspective)
@State private var position: MapCameraPosition = .camera(
MapCamera(centerCoordinate: applePark, distance: 1000, heading: 90, pitch: 60)
)
// Frame specific items
position = .item(MKMapItem.forCurrentLocation())
position = .rect(MKMapRect(...))
マップスタイル
デフォルトは .standard とし、機能上必要な場合にのみ .imagery または .hybrid、リアルな標高、交通情報、ポイントオブインタレストのフィルタリングを選択します。マップビューの完全なセットアップを参照してください。
マップの操作モード
インタラクティブなマップには .all を使用します。ジェスチャーの調整を意図する場合にのみモードを制限し、静的な埋め込みマップには [] を使用します。リストまたは ScrollView 内のマップを参照してください。
マップの選択
@State private var selectedMarker: MKMapItem?
Map(selection: $selectedMarker) {
ForEach(places) { place in
Marker(place.name, coordinate: place.coordinate)
.tag(place.mapItem) // Tag must match selection type
}
}
.onChange(of: selectedMarker) { _, newValue in
guard let item = newValue else { return }
// React to selection
}
CoreLocation の最新 API
CLLocationUpdate.liveUpdates()(iOS 17 以降)
CLLocationManagerDelegate のコールバックを単一の非同期シーケンスに置き換えます。各反復では、任意の CLLocation を含む CLLocationUpdate が返されます。iOS 18 以降では、認証の拒否、位置情報サービス全体の無効化、位置情報の取得不可、使用中の条件不足などの診断状態に対して、永久に待機し続けるのではなく、状態の低下をユーザーに示す経路を用意します。機能からキャンセルできるようタスクを保持し、マップ UI やバックグラウンド処理に反映する前に、無効、不正確、古い、または使用できない移動データを除外します。
import CoreLocation
@MainActor
@Observable
final class LocationTracker {
var currentLocation: CLLocation?
private var updateTask: Task<Void, Never>?
func startTracking() {
updateTask = Task {
do {
let updates = CLLocationUpdate.liveUpdates()
for try await update in updates {
guard let location = update.location else { continue }
// Filter by horizontal accuracy
guard location.horizontalAccuracy >= 0,
location.horizontalAccuracy < 50 else { continue }
currentLocation = location
}
} catch is CancellationError {
// Expected when tracking stops.
} catch {
currentLocation = nil
}
}
}
func stopTracking() {
updateTask?.cancel()
updateTask = nil
}
}
CLServiceSession(iOS 18 以降)
機能の利用期間中に必要な認証要件を宣言します。位置情報サービスを使用する間は、セッションへの参照を保持します。
// When-in-use authorization with full accuracy preference
let session = CLServiceSession(
authorization: .whenInUse,
fullAccuracyPurposeKey: "NearbySearchPurpose"
)
// Hold `session` as a stored property; release it when done.
iOS 18 以降では、明示的に作成しない場合、CLLocationUpdate.liveUpdates() と CLMonitor は暗黙的な CLServiceSession を使用します。.always 認証または高精度が必要な場合は、明示的に作成してください。
認証フロー
// Info.plist keys (required):
// NSLocationWhenInUseUsageDescription
// NSLocationAlwaysAndWhenInUseUsageDescription (only if .always needed)
// Check authorization and guide user to Settings when denied
struct LocationPermissionView: View {
@Environment(\.openURL) private var openURL
var body: some View {
ContentUnavailableView {
Label("Location Access Denied", systemImage: "location.slash")
} description: {
Text("Enable location access in Settings to use this feature.")
} actions: {
Button("Open Settings") {
if let url = URL(string: UIApplication.openSettingsURLString) {
openURL(url)
}
}
}
}
}
ジオコーディング
CLGeocoder(iOS 8 以降)
let geocoder = CLGeocoder()
// Forward geocoding: address string -> coordinates
let placemarks = try await geocoder.geocodeAddressString("1 Apple Park Way, Cupertino")
if let location = placemarks.first?.location {
print(location.coordinate) // CLLocationCoordinate2D
}
// Reverse geocoding: coordinates -> placemark
let location = CLLocation(latitude: 37.3349, longitude: -122.0090)
let placemarks = try await geocoder.reverseGeocodeLocation(location)
if let placemark = placemarks.first {
let address = [placemark.name, placemark.locality, placemark.administrativeArea]
.compactMap { $0 }
.joined(separator: ", ")
}
MKGeocodingRequest と MKReverseGeocodingRequest(iOS 26 以降)
MapKit ネイティブの新しいジオコーディング機能です。より豊富なデータを含む MKMapItem と、柔軟な住所形式に対応する MKAddress/MKAddressRepresentations を返します。
@available(iOS 26, *)
func reverseGeocode(location: CLLocation) async throws -> MKMapItem? {
guard let request = MKReverseGeocodingRequest(location: location) else {
return nil
}
let mapItems = try await request.mapItems
return mapItems.first
}
@available(iOS 26, *)
func forwardGeocode(address: String) async throws -> [MKMapItem] {
guard let request = MKGeocodingRequest(addressString: address) else { return [] }
return try await request.mapItems
}
検索
MKLocalSearchCompleter(オートコンプリート)
@Observable
final class SearchCompleter: NSObject, MKLocalSearchCompleterDelegate {
var results: [MKLocalSearchCompletion] = []
var query: String = "" { didSet { completer.queryFragment = query } }
private let completer = MKLocalSearchCompleter()
override init() {
super.init()
completer.delegate = self
completer.resultTypes = [.address, .pointOfInterest]
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
results = completer.results
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
results = []
}
}
MKLocalSearch(全文検索)
func search(for completion: MKLocalSearchCompletion) async throws -> [MKMapItem] {
let request = MKLocalSearch.Request(completion: completion)
request.resultTypes = [.pointOfInterest, .address]
let search = MKLocalSearch(request: request)
let response = try await search.start()
return response.mapItems
}
// Search by natural language query within a region
func searchNearby(query: String, region: MKCoordinateRegion) async throws -> [MKMapItem] {
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = query
request.region = region
let search = MKLocalSearch(request: request)
let response = try await search.start()
return response.mapItems
}
経路案内
func getDirections(from source: MKMapItem, to destination: MKMapItem,
transport: MKDirectionsTransportType = .automobile) async throws -> MKRoute? {
let request = MKDirections.Request()
request.source = source
request.destination = destination
request.transportType = transport
let directions = MKDirections(request: request)
let response = try await directions.calculate()
return response.routes.first
}
地図上に経路を表示する
@State private var route: MKRoute?
Map {
if let route {
MapPolyline(route.polyline)
.stroke(.blue, lineWidth: 5)
}
Marker("Start", coordinate: startCoord)
Marker("End", coordinate: endCoord)
}
.task {
route = try? await getDirections(from: startItem, to: endItem)
}
所要時間の計算
func getETA(from source: MKMapItem, to destination: MKMapItem) async throws -> TimeInterval {
let request = MKDirections.Request()
request.source = source
request.destination = destination
let directions = MKDirections(request: request)
let response = try await directions.calculateETA()
return response.expectedTravelTime
}
自転車の経路案内(iOS 14 以降)
リクエストの移動手段の種類として .cycling を使用してください。完全な例については、
自転車の経路をご覧ください。
PlaceDescriptor(iOS 26 以降)
Place ID がなくても、座標や住所から詳細な場所の参照を作成できます。import GeoToolbox が必要です。
import GeoToolbox
@available(iOS 26, *)
func lookupPlace(name: String, coordinate: CLLocationCoordinate2D) async throws -> MKMapItem {
let descriptor = PlaceDescriptor(
representations: [.coordinate(coordinate)],
commonName: name
)
let request = MKMapItemRequest(placeDescriptor: descriptor)
return try await request.mapItem
}
よくある間違い
しないでください: 最初から「常に許可」の認証をリクエストする。
してください: まずは使用中のみの認証から始めます。iOS 18 以降では、機能の利用期間中は CLServiceSession を保持します。終了後にシステムによる再起動が必要なバックグラウンド機能に限り、.always をリクエストしてください。
しないでください: iOS 17 以降で、単純な位置情報の取得に CLLocationManagerDelegate を使用する。
してください: より簡潔で読みやすいコードにするため、非同期ストリーム CLLocationUpdate.liveUpdates() を使用します。
しないでください: 拒否、全体での拒否、位置情報の利用不可など、CLLocationUpdate の診断情報を無視する。
してください: 機能を停止または縮退させ、設定への誘導などの復旧用 UI を表示し、検索や手動操作のフローは引き続き利用できるようにします。
しないでください: 地図やビューがなくなった後も、所有者のないタスクで liveUpdates() を実行し続ける。
してください: Task を保持し、機能の停止時にキャンセルします。また、無効、不正確、古い、またはあり得ない移動を示す位置情報を除外します。
しないでください: CLPlacemark のプロパティを強制アンラップする。すべてオプショナルです。
してください: nil 合体演算子を使用します:placemark.locality ?? "Unknown"。
しないでください: キー入力のたびに MKLocalSearchCompleter のクエリを実行する。
してください: .task(id: searchText) と Task.sleep(for: .milliseconds(300)) を組み合わせてデバウンスします。
しないでください: 位置情報の認証が拒否された際に、何も通知せずに失敗する。
してください: .denied 状態を検出し、設定画面へのディープリンクを含むアラートを表示します。
しないでください: ジオコーディングが常に成功すると想定する。結果が空の場合やネットワークエラーを処理してください。
レビューチェックリスト
- Info.plist に具体的な理由を記載した
NSLocationWhenInUseUsageDescriptionがある - 認証拒否時に設定画面へのディープリンクで対処する
- 不要になったときに
CLLocationUpdateのタスクをキャンセルする(バッテリー対策) - ユースケースに適した位置情報の精度を使用する
- 地図のアノテーションで、安定した ID を持つ
Identifiableデータを使用する - ジオコーディングエラーを処理する(ネットワーク障害、結果なし)
- 検索補完の入力をデバウンスする
-
CLMonitorの条件数を 20 件に制限し、インスタンスを保持する - バックグラウンド位置情報で
CLBackgroundActivitySessionを使用する - VoiceOver で地図をテストする
- 地図のアノテーションのビューモデルと位置情報 UI の更新を
@MainActorで分離する
参考資料
- references/mapkit-patterns.md — 地図のセットアップ、アノテーション、検索、経路、クラスタリング、Look Around、スナップショット。
- references/mapkit-corelocation-patterns.md — CLLocationUpdate、CLMonitor、CLServiceSession、バックグラウンド位置情報、テスト。