Integrate Amap SDK to replace CoreLocation and MapKit for all location features.
Unify GPS, reverse geocoding, and map display behind LocationProvider with privacy-gated bootstrap after login, aligned with Android. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
100
suixinkan/Features/Location/LocationProvider.swift
Normal file
100
suixinkan/Features/Location/LocationProvider.swift
Normal file
@ -0,0 +1,100 @@
|
||||
//
|
||||
// LocationProvider.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
import Foundation
|
||||
|
||||
/// 统一定位服务,封装高德 `AMapLocationManager`,对齐 Android `LocationProvider`。
|
||||
final class LocationProvider: NSObject, LocationProviding {
|
||||
|
||||
static let shared = LocationProvider()
|
||||
|
||||
private let permissionManager = CLLocationManager()
|
||||
private let geocoder: LocationGeocoder
|
||||
|
||||
init(geocoder: LocationGeocoder = .shared) {
|
||||
self.geocoder = geocoder
|
||||
super.init()
|
||||
permissionManager.desiredAccuracy = kCLLocationAccuracyBest
|
||||
}
|
||||
|
||||
func requestSnapshot() async throws -> HomeLocationSnapshot {
|
||||
try AMapBootstrap.requireConfigured()
|
||||
try await ensureLocationPermission()
|
||||
|
||||
let manager = makeLocationManager()
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
let started = manager.requestLocation(withReGeocode: true) { location, regeocode, error in
|
||||
if let error {
|
||||
continuation.resume(throwing: error)
|
||||
return
|
||||
}
|
||||
guard let location else {
|
||||
continuation.resume(throwing: LocationProviderError.locationFailed)
|
||||
return
|
||||
}
|
||||
let address = regeocode?.formattedAddress ?? ""
|
||||
continuation.resume(returning: HomeLocationSnapshot(
|
||||
latitude: location.coordinate.latitude,
|
||||
longitude: location.coordinate.longitude,
|
||||
address: address
|
||||
))
|
||||
}
|
||||
if !started {
|
||||
continuation.resume(throwing: LocationProviderError.locationFailed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func requestCoordinate() async throws -> CLLocationCoordinate2D {
|
||||
try AMapBootstrap.requireConfigured()
|
||||
try await ensureLocationPermission()
|
||||
|
||||
let manager = makeLocationManager()
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
let started = manager.requestLocation(withReGeocode: false) { location, _, error in
|
||||
if let error {
|
||||
continuation.resume(throwing: error)
|
||||
return
|
||||
}
|
||||
guard let location else {
|
||||
continuation.resume(throwing: LocationProviderError.locationFailed)
|
||||
return
|
||||
}
|
||||
continuation.resume(returning: location.coordinate)
|
||||
}
|
||||
if !started {
|
||||
continuation.resume(throwing: LocationProviderError.locationFailed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func reverseGeocode(latitude: Double, longitude: Double) async -> String {
|
||||
await geocoder.reverseGeocode(latitude: latitude, longitude: longitude)
|
||||
}
|
||||
|
||||
/// 请求系统定位权限,首次弹窗后等待用户响应。
|
||||
func ensureLocationPermission() async throws {
|
||||
let initialStatus = permissionManager.authorizationStatus
|
||||
if initialStatus == .notDetermined {
|
||||
permissionManager.requestWhenInUseAuthorization()
|
||||
try await Task.sleep(nanoseconds: 500_000_000)
|
||||
}
|
||||
|
||||
let status = permissionManager.authorizationStatus
|
||||
guard status == .authorizedWhenInUse || status == .authorizedAlways else {
|
||||
throw LocationProviderError.permissionDenied
|
||||
}
|
||||
}
|
||||
|
||||
private func makeLocationManager() -> AMapLocationManager {
|
||||
let manager = AMapLocationManager()
|
||||
manager.desiredAccuracy = kCLLocationAccuracyBest
|
||||
manager.locationTimeout = 30
|
||||
manager.reGeocodeTimeout = 10
|
||||
manager.locatingWithReGeocode = true
|
||||
return manager
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user