This skill should be used when the user asks to "add map view to macOS app", "show user location on macOS", "implement MapKit for macOS", "add markers to map in macOS app", "calculate route in macOS...
macOS 14+ (Sonoma) 向けSwiftUI/AppKitアプリケーションにおけるMapKit実装ガイド。地図表示、位置情報、経路検索、周辺検索機能の実装をサポートする。
MapKit機能を実装する際の必須手順:
Info.plist設定
NSLocationUsageDescription - 位置情報アクセス理由エンタイトルメント設定 (App Sandbox)
com.apple.security.personal-information.location - 位置情報アクセスcom.apple.security.network.client - ネットワークアクセス(位置情報サービスに必要)フレームワークインポート
import MapKit
import CoreLocation
位置情報権限リクエスト
CLLocationManager.authorizationStatusで状態確認requestWhenInUseAuthorization()で権限リクエスト地図表示
macOS 14で刷新されたMap APIを使用。
@State private var position: MapCameraPosition = .automatic
var body: some View {
Map(position: $position) {
Marker("Tokyo", coordinate: .tokyo)
UserAnnotation()
}
.mapStyle(.standard(elevation: .realistic))
.mapControls {
MapUserLocationButton()
MapCompass()
MapScaleView()
MapZoomStepper() // macOS向け
}
}
主要プロパティ:
position - カメラ位置(.automatic, .userLocation(), .region(), .camera())mapStyle - 地図スタイル(.standard, .imagery, .hybrid)interactionModes - 操作モード(.all, .pan, .zoom, .rotate, .pitch)カメラ位置を制御する構造体。
// ユーザー位置を追従
let position: MapCameraPosition = .userLocation(fallback: .automatic)
// 特定の地域を表示
let position: MapCameraPosition = .region(MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 35.6762, longitude: 139.6503),
span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
))
// カメラアングル指定
let position: MapCameraPosition = .camera(MapCamera(
centerCoordinate: CLLocationCoordinate2D(latitude: 35.6762, longitude: 139.6503),
distance: 1000,
heading: 45,
pitch: 60
))
地図の外観を設定。
// 標準スタイル
.mapStyle(.standard)
// 3D建物付き標準スタイル
.mapStyle(.standard(elevation: .realistic))
// 衛星写真
.mapStyle(.imagery)
// 衛星写真 + 道路名
.mapStyle(.hybrid)
// POIフィルタリング
.mapStyle(.standard(pointsOfInterest: .including([.restaurant, .cafe])))
システム提供のマーカー。
Map {
// 基本マーカー
Marker("Tokyo Station", coordinate: .tokyoStation)
// システムイメージ付き
Marker("Restaurant", systemImage: "fork.knife", coordinate: location)
.tint(.orange)
// モノグラム
Marker("A", monogram: Text("A"), coordinate: location)
}
カスタムビューをマーカーとして表示。
Map {
Annotation("Custom", coordinate: location) {
VStack {
Image(systemName: "star.fill")
.foregroundStyle(.yellow)
Text("Spot")
.font(.caption)
}
.padding(4)
.background(.white)
.clipShape(RoundedRectangle(cornerRadius: 8))
}
}
ユーザーの現在地を表示。
Map {
UserAnnotation()
}
.mapControls {
MapUserLocationButton()
}
macOS用位置情報マネージャーの設定。
@MainActor
@Observable
final class LocationManager: NSObject, CLLocationManagerDelegate {
var location: CLLocation?
var authorizationStatus: CLAuthorizationStatus = .notDetermined
private let manager = CLLocationManager()
override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
}
func requestAuthorization() {
manager.requestWhenInUseAuthorization()
}
func startUpdating() {
manager.startUpdatingLocation()
}
nonisolated func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
Task { @MainActor in
location = locations.last
}
}
nonisolated func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
Task { @MainActor in
authorizationStatus = manager.authorizationStatus
// macOSでは.authorizedが主に使用される
switch authorizationStatus {
case .authorized, .authorizedAlways:
startUpdating()
case .denied, .restricted:
// システム環境設定へ誘導
openLocationSettings()
case .notDetermined:
break
@unknown default:
break
}
}
}
private func openLocationSettings() {
if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_LocationServices") {
NSWorkspace.shared.open(url)
}
}
}
| State | 説明 | 対応 |
|---|---|---|
.authorized |
許可(macOS主要) | 位置情報取得可能 |
.authorizedAlways |
常時許可 | 位置情報取得可能 |
.denied |
拒否された | システム環境設定へ誘導 |
.restricted |
制限されている | 機能無効化 |
.notDetermined |
未決定 | 権限リクエスト実行 |
注意: macOSでは.authorizedWhenInUseはiOSほど一般的ではなく、.authorizedまたは.authorizedAlwaysが返されることが多い。
AppKitアプリでMKMapViewを使用する場合。
import SwiftUI
import MapKit
struct AppKitMapView: NSViewRepresentable {
@Binding var region: MKCoordinateRegion
var annotations: [MKAnnotation]
func makeNSView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
mapView.showsUserLocation = true
mapView.showsCompass = true
mapView.showsZoomControls = true
return mapView
}
func updateNSView(_ mapView: MKMapView, context: Context) {
mapView.setRegion(region, animated: true)
// アノテーション更新
mapView.removeAnnotations(mapView.annotations)
mapView.addAnnotations(annotations)
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var parent: AppKitMapView
init(_ parent: AppKitMapView) {
self.parent = parent
}
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
parent.region = mapView.region
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !(annotation is MKUserLocation) else { return nil }
let identifier = "CustomAnnotation"
var view = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKMarkerAnnotationView
if view == nil {
view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view?.canShowCallout = true
} else {
view?.annotation = annotation
}
return view
}
}
}
経路検索を実行。
func calculateRoute(from source: CLLocationCoordinate2D, to destination: CLLocationCoordinate2D) async throws -> MKRoute {
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: source))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: destination))
request.transportType = .automobile
let directions = MKDirections(request: request)
let response = try await directions.calculate()
guard let route = response.routes.first else {
throw MapError.noRouteFound
}
return route
}
経路をMapに表示。
@State private var route: MKRoute?
var body: some View {
Map {
if let route {
MapPolyline(route.polyline)
.stroke(.blue, lineWidth: 5)
}
}
}
ターンバイターン案内情報を取得。
for step in route.steps {
print("距離: \(step.distance)m")
print("案内: \(step.instructions)")
}
周辺検索を実行。
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
}
検索サジェストを提供。
@Observable
final class SearchCompleter: NSObject, MKLocalSearchCompleterDelegate {
var results: [MKLocalSearchCompletion] = []
private let completer = MKLocalSearchCompleter()
override init() {
super.init()
completer.delegate = self
completer.resultTypes = [.address, .pointOfInterest]
}
func search(query: String) {
completer.queryFragment = query
}
nonisolated func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
Task { @MainActor in
results = completer.results
}
}
}
Look Aroundシーンを取得。
func getLookAroundScene(for coordinate: CLLocationCoordinate2D) async -> MKLookAroundScene? {
let request = MKLookAroundSceneRequest(coordinate: coordinate)
return try? await request.scene
}
プレビューを埋め込み表示。
@State private var lookAroundScene: MKLookAroundScene?
var body: some View {
VStack {
if let scene = lookAroundScene {
LookAroundPreview(scene: scene)
.frame(height: 300)
} else {
ContentUnavailableView("Look Around利用不可", systemImage: "eye.slash")
}
}
.task {
lookAroundScene = await getLookAroundScene(for: coordinate)
}
}
macOS制限事項:
.imageryスタイル)を使用することを推奨func geocode(address: String) async throws -> CLLocationCoordinate2D {
let geocoder = CLGeocoder()
let placemarks = try await geocoder.geocodeAddressString(address)
guard let location = placemarks.first?.location else {
throw MapError.geocodingFailed
}
return location.coordinate
}
func reverseGeocode(coordinate: CLLocationCoordinate2D) async throws -> String {
let geocoder = CLGeocoder()
let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
let placemarks = try await geocoder.reverseGeocodeLocation(location)
guard let placemark = placemarks.first else {
throw MapError.geocodingFailed
}
return [placemark.locality, placemark.thoroughfare, placemark.subThoroughfare]
.compactMap { $0 }
.joined(separator: " ")
}
円形オーバーレイ。
Map {
MapCircle(center: coordinate, radius: 500)
.foregroundStyle(.blue.opacity(0.3))
.stroke(.blue, lineWidth: 2)
}
多角形オーバーレイ。
Map {
MapPolygon(coordinates: polygonCoordinates)
.foregroundStyle(.green.opacity(0.3))
.stroke(.green, lineWidth: 2)
}
macOSアプリはApp Sandboxを使用する場合、位置情報にアクセスするためにエンタイトルメントが必要。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.personal-information.location</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
重要: com.apple.security.network.client(送信接続)は位置情報サービスにも必要。これがないと位置情報が取得できない場合がある。
MapKit/CoreLocationで発生しうる主要エラー:
| エラー | 原因 | 対処 |
|---|---|---|
CLError.denied |
位置情報が拒否された | システム環境設定へ誘導 |
CLError.locationUnknown |
位置を特定できない | リトライまたはフォールバック |
MKError.serverFailure |
サーバーエラー | リトライ |
MKError.directionsNotFound |
経路が見つからない | 代替手段を提案 |
| エンタイトルメント不足 | Sandbox設定漏れ | エンタイトルメント確認 |
詳細なトラブルシューティングは references/troubleshooting-macos.md を参照。
| Component | Purpose | Key API |
|---|---|---|
Map |
地図表示 | Map(position:) { } |
MapCameraPosition |
カメラ制御 | .automatic, .region(), .camera() |
Marker |
マーカー表示 | Marker(_:coordinate:) |
Annotation |
カスタムマーカー | Annotation(_:coordinate:) { } |
CLLocationManager |
位置情報管理 | startUpdatingLocation() |
MKDirections |
経路検索 | calculate() |
MKLocalSearch |
周辺検索 | start() |
CLGeocoder |
ジオコーディング | geocodeAddressString(_:) |
LookAroundPreview |
Look Around表示 | LookAroundPreview(scene:) |
MKMapView |
AppKit地図 | NSViewRepresentable |
詳細な情報は以下を参照:
references/mapkit-swiftui.md - MapKit for SwiftUI API詳細リファレンスreferences/core-location-macos.md - macOS Core Location統合の詳細references/appkit-integration.md - AppKit MKMapView統合references/sandbox-entitlements.md - App Sandbox設定詳細references/permission-patterns-macos.md - macOS権限リクエストパターンreferences/geocoding.md - ジオコーディングの詳細設定references/directions.md - 経路検索APIリファレンスreferences/local-search.md - ローカル検索の詳細references/look-around-macos.md - Look Around機能(macOS制限事項)references/custom-overlays.md - カスタムオーバーレイの実装references/window-integration.md - ウィンドウ管理統合references/troubleshooting-macos.md - macOS特有のトラブルシューティング実装サンプルは examples/ ディレクトリを参照:
examples/basic-map-view.swift - 基本的な地図表示examples/appkit-mapview.swift - AppKit MKMapView統合examples/location-manager-macos.swift - macOS用位置情報マネージャーexamples/marker-annotation-view.swift - マーカー/アノテーション実装examples/directions-view.swift - 経路検索・ナビゲーション画面examples/local-search-view.swift - ローカル検索UIexamples/look-around-view.swift - Look Around統合examples/info-plist-config.xml - Info.plist設定テンプレートexamples/entitlements-config.xml - エンタイトルメント設定テンプレート