Skip to main content

User Location 関連

感想/まとめ

現在地の許可設定 (iOS でお決まりの) だけ対処しておけば、
以下のことが簡単に、1行足すだけレベルで出来る。 (2と3はセット)

  1. 現在地と向きを表示するアイコン (Puck という) の表示
  2. 視点(viewport, camera) を現在地に移動する
  3. アイコンを現在地に追従させる (.followPuck)

現在地の許可設定は、もうちょっとこなれる必要がありそうだが、
Mapbox でなく iOS の話なので、別のメモにでも記載しよう

Info.plist に設定するもの (Mapbox でなく iOS の話)

項目一覧

#キー表示名メモ
1NSLocationWhenInUseUsageDescriptionPrivacy - Location When In Use Usage Descriptionアプリ使用中のみ位置情報を使う場合に使用
2NSLocationAlwaysAndWhenInUseUsageDescriptionPrivacy - Location Always and When In Use Usage Descriptionバックグラウンドでも許可したい場合に使用
3NSLocationTemporaryUsageDescriptionDictionaryPrivacy - Location Temporary Usage Description Dictionary高精度の位置情報が必要の場合に使用

→ 「アプリ使用中のみ」許可されれば十分なので、いったん「1」のみ選択としよう

設定例

Info.plist
<key>NSLocationWhenInUseUsageDescription</key>
<string>TODO .. Description 1.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>TODO .. Description 2.</string>
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
<key>PreciseLocation</key>
<string>TODO .. Description 3.</string>
</dict>

→ 実際は必要なキーだけ載せる

「2」の注意点

  • もし「2」を選択する場合は「1」も必要(「どちらか一方」ではないということ)
  • Background Modes で Location Updates をON

「3」の注意点

  • 「3」の使用時は requestTemporaryFullAccuracyAuthorization() 呼び出しが必要
  • 実は「3」のサブキー (PreciseLocation) は実は何でも良いが、関数の引数にわたすキーと info.plist で揃える必要がある
swift
if locationManager.accuracyAuthorization == .reducedAccuracy {
locationManager.requestTemporaryFullAccuracyAuthorization(
withPurposeKey: "PreciseLocation"
)
}

現在地にアイコンを表示する

こう書くだけ。

swift
Map {
...
Puck2D()
}

方向も見せるには bearing オプションをつける

コード動作
Puck2D()矢印なし (=円が出るだけ)
Puck2D(bearing: .heading)端末を向けている方向に矢印
Puck2D(bearing: .course)移動の進行方向に矢印

現在地へ飛ぶ+追従させる

マップをこのように表示していたとして、

swift
struct ViewportExample: View {
@State var viewport = Viewport.styleDefault

var body: some View {
Map(viewport: $viewport) {
...
}
}
}

onAppear やボタンなどから、以下を呼ぶだけ。

swift
viewport = .followPuck(zoom: 10)
  • 追従中にユーザーがパン/ズームすると追従は解除される
  • 再度追従したいときはもう一度 .followPuck(...) を設定する必要あり

例: 現在の zoom を変えずに位置だけ移動

zoom 引数は省略不可なので、zoom を変えたくない場合は、MapReader から取得したものを指定。

マップ側

swift
.onChange(of: appData.followPuckTrigger) {
withViewportAnimation {
viewport = .followPuck(zoom: mapReader.map?.cameraState.zoom ?? 10)
}
}

ボタン側 (ZStack で Map に重ねる)

swift
Button {
appData.followPuckTrigger = UUID()
} label: {
Image(systemName: "location.fill")
.font(.title2)
}

※ Viewport について余談: ちなみに、座標全部を表示したい場合はこう

swift
let geometry = Geometry.lineString(LineString(coords))
viewport = .overview(geometry: geometry).padding(.all, 100)

位置情報許可 関連のコード

もうちょっと習熟必要

swift
@StateObject private var locationManager = CLLocationManagerDelegateWrapper()

if let loc = locationManager.lastLocation {
viewport = .followPuck(zoom: 10)
} else {
// デフォルト
let center = CLLocationCoordinate2D(latitude: 35.6598, longitude: 139.702389)
let camera = Viewport.camera(center: center, zoom: 13, bearing: 0, pitch: 0)
viewport = camera
}
...
swift
import CoreLocation

final class CLLocationManagerDelegateWrapper: NSObject, ObservableObject, CLLocationManagerDelegate {
@Published var lastLocation: CLLocation?
private let manager = CLLocationManager()

override init() {
super.init()
manager.delegate = self
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
}

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
lastLocation = locations.last
}
}
swift
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .authorizedWhenInUse, .authorizedAlways:
manager.startUpdatingLocation()
default:
manager.stopUpdatingLocation()
}
}