User Location 関連
感想/まとめ
現在地の許可設定 (iOS でお決まりの) だけ対処しておけば、
以下のことが簡単に、1行足すだけレベルで出来る。 (2と3はセット)
- 現在地と向きを表示するアイコン (Puck という) の表示
- 視点(viewport, camera) を現在地に移動する
- アイコンを現在地に追従させる (.followPuck)
現在地の許可設定は、もうちょっとこなれる必要がありそうだが、
Mapbox でなく iOS の話なので、別のメモにでも記載しよう
Info.plist に設定するもの (Mapbox でなく iOS の話)
項目一覧
# | キー | 表示名 | メモ |
---|---|---|---|
1 | NSLocationWhenInUseUsageDescription | Privacy - Location When In Use Usage Description | アプリ使用中のみ位置情報を使う場合に使用 |
2 | NSLocationAlwaysAndWhenInUseUsageDescription | Privacy - Location Always and When In Use Usage Description | バックグラウンドでも許可したい場合に使用 |
3 | NSLocationTemporaryUsageDescriptionDictionary | Privacy - 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()
}
}