集成高德 SDK 并替换所有定位相关的 CoreLocation 与 MapKit
This commit is contained in:
@ -23,6 +23,10 @@ enum AuthSessionHelper {
|
||||
AppStore.shared.privacyAgreementAccepted = privacyAgreementAccepted
|
||||
applyAccountSession(from: selectedAccount, in: response)
|
||||
|
||||
if privacyAgreementAccepted {
|
||||
AMapBootstrap.configureIfNeeded()
|
||||
}
|
||||
|
||||
NotificationCenter.default.post(name: NotificationName.userDidLogin, object: nil)
|
||||
}
|
||||
|
||||
|
||||
@ -1,98 +0,0 @@
|
||||
//
|
||||
// HomeLocationProvider.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
|
||||
/// 首页位置采集结果。
|
||||
struct HomeLocationSnapshot: Sendable {
|
||||
let latitude: Double
|
||||
let longitude: Double
|
||||
let address: String
|
||||
}
|
||||
|
||||
/// 首页位置采集,基于 CoreLocation 并提供可选逆地理。
|
||||
final class HomeLocationProvider: NSObject, CLLocationManagerDelegate {
|
||||
|
||||
private let manager = CLLocationManager()
|
||||
private var coordinateContinuation: CheckedContinuation<CLLocationCoordinate2D, Error>?
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
manager.delegate = self
|
||||
manager.desiredAccuracy = kCLLocationAccuracyBest
|
||||
}
|
||||
|
||||
/// 请求当前位置与地址。
|
||||
func requestSnapshot() async throws -> HomeLocationSnapshot {
|
||||
let status = manager.authorizationStatus
|
||||
if status == .notDetermined {
|
||||
manager.requestWhenInUseAuthorization()
|
||||
try await Task.sleep(nanoseconds: 300_000_000)
|
||||
}
|
||||
|
||||
let resolvedStatus = manager.authorizationStatus
|
||||
guard resolvedStatus == .authorizedWhenInUse || resolvedStatus == .authorizedAlways else {
|
||||
throw HomeLocationProviderError.permissionDenied
|
||||
}
|
||||
|
||||
let coordinate = try await requestCoordinate()
|
||||
let address = await reverseGeocode(latitude: coordinate.latitude, longitude: coordinate.longitude)
|
||||
return HomeLocationSnapshot(
|
||||
latitude: coordinate.latitude,
|
||||
longitude: coordinate.longitude,
|
||||
address: address
|
||||
)
|
||||
}
|
||||
|
||||
/// 逆地理编码,供地图标记使用。
|
||||
func reverseGeocode(latitude: Double, longitude: Double) async -> String {
|
||||
let location = CLLocation(latitude: latitude, longitude: longitude)
|
||||
let geocoder = CLGeocoder()
|
||||
do {
|
||||
let placemarks = try await geocoder.reverseGeocodeLocation(location)
|
||||
return placemarks.first?.formattedAddress ?? ""
|
||||
} catch {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
private func requestCoordinate() async throws -> CLLocationCoordinate2D {
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
self.coordinateContinuation = continuation
|
||||
manager.requestLocation()
|
||||
}
|
||||
}
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||
guard let location = locations.last else { return }
|
||||
coordinateContinuation?.resume(returning: location.coordinate)
|
||||
coordinateContinuation = nil
|
||||
}
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
||||
coordinateContinuation?.resume(throwing: error)
|
||||
coordinateContinuation = nil
|
||||
}
|
||||
}
|
||||
|
||||
/// 首页定位错误。
|
||||
enum HomeLocationProviderError: LocalizedError {
|
||||
case permissionDenied
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .permissionDenied: "需要定位权限才能使用此功能"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension CLPlacemark {
|
||||
var formattedAddress: String {
|
||||
[subLocality, locality, administrativeArea]
|
||||
.compactMap { $0?.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||
.filter { !$0.isEmpty }
|
||||
.joined(separator: "")
|
||||
}
|
||||
}
|
||||
@ -30,7 +30,7 @@ final class HomeViewModel {
|
||||
let locationReportService: LocationReportService
|
||||
private let appStore: AppStore
|
||||
private let commonMenuStore: HomeCommonMenuStore
|
||||
private let locationProvider: HomeLocationProvider
|
||||
private let locationProvider: any LocationProviding
|
||||
|
||||
private var timeoutDialogTriggered = false
|
||||
private var dismissedLocationTimeoutDialog = false
|
||||
@ -39,7 +39,7 @@ final class HomeViewModel {
|
||||
appStore: AppStore = .shared,
|
||||
locationStateStore: HomeLocationStateStore = HomeLocationStateStore(),
|
||||
commonMenuStore: HomeCommonMenuStore = HomeCommonMenuStore(),
|
||||
locationProvider: HomeLocationProvider = HomeLocationProvider()
|
||||
locationProvider: any LocationProviding = LocationProvider.shared
|
||||
) {
|
||||
self.appStore = appStore
|
||||
self.locationStateStore = locationStateStore
|
||||
|
||||
@ -27,11 +27,11 @@ final class ScenicSelectionViewModel {
|
||||
var onNavigateApplyStatus: ((String) -> Void)?
|
||||
|
||||
private let appStore: AppStore
|
||||
private let locationProvider: HomeLocationProvider
|
||||
private let locationProvider: any LocationProviding
|
||||
private var currentLat: Double?
|
||||
private var currentLng: Double?
|
||||
|
||||
init(appStore: AppStore = .shared, locationProvider: HomeLocationProvider = HomeLocationProvider()) {
|
||||
init(appStore: AppStore = .shared, locationProvider: any LocationProviding = LocationProvider.shared) {
|
||||
self.appStore = appStore
|
||||
self.locationProvider = locationProvider
|
||||
}
|
||||
@ -63,7 +63,7 @@ final class ScenicSelectionViewModel {
|
||||
let address = snapshot.address.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
currentLocationText = address.isEmpty ? "最近的景区" : address
|
||||
loadFromLocal()
|
||||
} catch HomeLocationProviderError.permissionDenied {
|
||||
} catch LocationProviderError.permissionDenied {
|
||||
onShowMessage?("未授予定位权限,无法获取附近景区")
|
||||
} catch {
|
||||
onShowMessage?("定位失败: \(error.localizedDescription)")
|
||||
|
||||
49
suixinkan/Features/Location/AMapBootstrap.swift
Normal file
49
suixinkan/Features/Location/AMapBootstrap.swift
Normal file
@ -0,0 +1,49 @@
|
||||
//
|
||||
// AMapBootstrap.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 高德 SDK 隐私合规与 Key 初始化,必须在实例化任何 SDK 类之前完成。
|
||||
enum AMapBootstrap {
|
||||
|
||||
private(set) static var isConfigured = false
|
||||
private static let lock = NSLock()
|
||||
|
||||
/// 在用户已同意 App 隐私协议后配置高德 SDK,幂等。
|
||||
@discardableResult
|
||||
static func configureIfNeeded(appStore: AppStore = .shared) -> Bool {
|
||||
guard appStore.privacyAgreementAccepted else { return false }
|
||||
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
guard !isConfigured else { return true }
|
||||
|
||||
AMapLocationManager.updatePrivacyShow(.didShow, privacyInfo: .didContain)
|
||||
AMapLocationManager.updatePrivacyAgree(.didAgree)
|
||||
MAMapView.updatePrivacyShow(.didShow, privacyInfo: .didContain)
|
||||
MAMapView.updatePrivacyAgree(.didAgree)
|
||||
AMapSearchAPI.updatePrivacyShow(.didShow, privacyInfo: .didContain)
|
||||
AMapSearchAPI.updatePrivacyAgree(.didAgree)
|
||||
|
||||
AMapServices.shared().apiKey = AMapConfig.apiKey
|
||||
AMapServices.shared().enableHTTPS = true
|
||||
isConfigured = true
|
||||
return true
|
||||
}
|
||||
|
||||
/// 要求高德 SDK 已完成合规初始化,否则抛出错误。
|
||||
static func requireConfigured(appStore: AppStore = .shared) throws {
|
||||
guard configureIfNeeded(appStore: appStore) else {
|
||||
throw LocationProviderError.privacyNotAccepted
|
||||
}
|
||||
}
|
||||
|
||||
/// 测试专用:重置初始化状态。
|
||||
static func resetForTesting() {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
isConfigured = false
|
||||
}
|
||||
}
|
||||
64
suixinkan/Features/Location/LocationGeocoder.swift
Normal file
64
suixinkan/Features/Location/LocationGeocoder.swift
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// LocationGeocoder.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 高德逆地理编码封装,对齐 Android `GeocodeSearch`。
|
||||
final class LocationGeocoder: NSObject, AMapSearchDelegate {
|
||||
|
||||
static let shared = LocationGeocoder()
|
||||
|
||||
private var searchAPI: AMapSearchAPI?
|
||||
private var continuation: CheckedContinuation<String, Never>?
|
||||
|
||||
private override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
/// 将坐标解析为可读地址。
|
||||
func reverseGeocode(latitude: Double, longitude: Double) async -> String {
|
||||
do {
|
||||
try AMapBootstrap.requireConfigured()
|
||||
} catch {
|
||||
return ""
|
||||
}
|
||||
|
||||
return await withCheckedContinuation { continuation in
|
||||
self.continuation?.resume(returning: "")
|
||||
self.continuation = continuation
|
||||
|
||||
guard let api = makeSearchAPI() else {
|
||||
continuation.resume(returning: "")
|
||||
return
|
||||
}
|
||||
let request = AMapReGeocodeSearchRequest()
|
||||
request.location = AMapGeoPoint.location(withLatitude: CGFloat(latitude), longitude: CGFloat(longitude))
|
||||
request.requireExtension = true
|
||||
request.radius = 200
|
||||
api.aMapReGoecodeSearch(request)
|
||||
}
|
||||
}
|
||||
|
||||
func onReGeocodeSearchDone(_ request: AMapReGeocodeSearchRequest!, response: AMapReGeocodeSearchResponse!) {
|
||||
let address = response?.regeocode?.formattedAddress ?? ""
|
||||
continuation?.resume(returning: address)
|
||||
continuation = nil
|
||||
}
|
||||
|
||||
func aMapSearchRequest(_ request: Any!, didFailWithError error: Error!) {
|
||||
continuation?.resume(returning: "")
|
||||
continuation = nil
|
||||
}
|
||||
|
||||
private func makeSearchAPI() -> AMapSearchAPI? {
|
||||
if let searchAPI {
|
||||
return searchAPI
|
||||
}
|
||||
guard let api = AMapSearchAPI() else { return nil }
|
||||
api.delegate = self
|
||||
searchAPI = api
|
||||
return api
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
54
suixinkan/Features/Location/LocationProviding.swift
Normal file
54
suixinkan/Features/Location/LocationProviding.swift
Normal file
@ -0,0 +1,54 @@
|
||||
//
|
||||
// LocationProviding.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
import Foundation
|
||||
|
||||
/// 位置采集结果。
|
||||
struct HomeLocationSnapshot: Sendable, Equatable {
|
||||
let latitude: Double
|
||||
let longitude: Double
|
||||
let address: String
|
||||
}
|
||||
|
||||
/// 定位服务协议,便于 ViewModel 注入与单元测试 mock。
|
||||
protocol LocationProviding: Sendable {
|
||||
/// 请求当前坐标与地址。
|
||||
func requestSnapshot() async throws -> HomeLocationSnapshot
|
||||
/// 请求当前坐标。
|
||||
func requestCoordinate() async throws -> CLLocationCoordinate2D
|
||||
/// 逆地理编码。
|
||||
func reverseGeocode(latitude: Double, longitude: Double) async -> String
|
||||
}
|
||||
|
||||
/// 定位错误。
|
||||
enum LocationProviderError: LocalizedError, Equatable {
|
||||
case privacyNotAccepted
|
||||
case permissionDenied
|
||||
case locationFailed
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .privacyNotAccepted:
|
||||
"请先阅读并同意隐私政策"
|
||||
case .permissionDenied:
|
||||
"需要定位权限才能使用此功能"
|
||||
case .locationFailed:
|
||||
"定位失败,请稍后重试"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 订单核销场景定位错误,保持原有提示文案。
|
||||
enum OrderLocationError: LocalizedError {
|
||||
case permissionDenied
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .permissionDenied:
|
||||
"缺少定位权限,功能无法进行"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -23,12 +23,12 @@ final class LocationReportService {
|
||||
|
||||
private let appStore: AppStore
|
||||
private let locationStateStore: HomeLocationStateStore
|
||||
private let locationProvider: HomeLocationProvider
|
||||
private let locationProvider: any LocationProviding
|
||||
|
||||
init(
|
||||
appStore: AppStore = .shared,
|
||||
locationStateStore: HomeLocationStateStore,
|
||||
locationProvider: HomeLocationProvider = HomeLocationProvider()
|
||||
locationProvider: any LocationProviding = LocationProvider.shared
|
||||
) {
|
||||
self.appStore = appStore
|
||||
self.locationStateStore = locationStateStore
|
||||
|
||||
@ -26,11 +26,11 @@ final class LocationReportViewModel {
|
||||
var onStateChange: (() -> Void)?
|
||||
var onShowMessage: ((String) -> Void)?
|
||||
|
||||
private let locationProvider: HomeLocationProvider
|
||||
private let locationProvider: any LocationProviding
|
||||
|
||||
init(
|
||||
locationStateStore: HomeLocationStateStore = HomeLocationStateStore(),
|
||||
locationProvider: HomeLocationProvider = HomeLocationProvider()
|
||||
locationProvider: any LocationProviding = LocationProvider.shared
|
||||
) {
|
||||
self.locationStateStore = locationStateStore
|
||||
self.locationProvider = locationProvider
|
||||
|
||||
@ -3,12 +3,11 @@
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
import UIKit
|
||||
|
||||
/// 主 Tab 中间扫码按钮结果处理,对齐 Android `MainTabViewModel.signIn`。
|
||||
@MainActor
|
||||
final class MainTabScanHandler: NSObject, CLLocationManagerDelegate {
|
||||
final class MainTabScanHandler {
|
||||
|
||||
var onShowMessage: ((String) -> Void)?
|
||||
var onShowSignIn: ((OrderSignInResponse) -> Void)?
|
||||
@ -18,15 +17,12 @@ final class MainTabScanHandler: NSObject, CLLocationManagerDelegate {
|
||||
var onNavigateBindAcquirer: ((Int) -> Void)?
|
||||
|
||||
private let api: OrderAPI
|
||||
private let locationManager = CLLocationManager()
|
||||
private var locationContinuation: ((Result<CLLocationCoordinate2D, Error>) -> Void)?
|
||||
private let locationProvider: any LocationProviding
|
||||
private(set) var pendingMultiVerify: MultiVerifyParseData?
|
||||
|
||||
init(api: OrderAPI) {
|
||||
init(api: OrderAPI, locationProvider: any LocationProviding = LocationProvider.shared) {
|
||||
self.api = api
|
||||
super.init()
|
||||
locationManager.delegate = self
|
||||
locationManager.desiredAccuracy = kCLLocationAccuracyBest
|
||||
self.locationProvider = locationProvider
|
||||
}
|
||||
|
||||
func handleScanResult(_ scanResult: String) async {
|
||||
@ -141,14 +137,8 @@ final class MainTabScanHandler: NSObject, CLLocationManagerDelegate {
|
||||
}
|
||||
|
||||
private func fetchSpotsForMultiVerify(pending: MultiVerifyParseData) async {
|
||||
let status = locationManager.authorizationStatus
|
||||
guard status == .authorizedWhenInUse || status == .authorizedAlways else {
|
||||
locationManager.requestWhenInUseAuthorization()
|
||||
onShowMessage?("缺少定位权限,功能无法进行")
|
||||
return
|
||||
}
|
||||
do {
|
||||
let coordinate = try await requestLocation()
|
||||
let coordinate = try await locationProvider.requestCoordinate()
|
||||
let response = try await api.multiTravelScenicSpotList(
|
||||
orderNumber: pending.orderNumber,
|
||||
lat: coordinate.latitude,
|
||||
@ -160,35 +150,12 @@ final class MainTabScanHandler: NSObject, CLLocationManagerDelegate {
|
||||
} else {
|
||||
onShowMultiVerifySheet?(spots, spots.first)
|
||||
}
|
||||
} catch LocationProviderError.permissionDenied {
|
||||
onShowMessage?(OrderLocationError.permissionDenied.errorDescription ?? "缺少定位权限,功能无法进行")
|
||||
} catch {
|
||||
onShowMessage?(error.localizedDescription)
|
||||
onShowMessage?((error as? LocalizedError)?.errorDescription ?? error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private func requestLocation() async throws -> CLLocationCoordinate2D {
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
locationContinuation = { result in
|
||||
switch result {
|
||||
case .success(let coordinate):
|
||||
continuation.resume(returning: coordinate)
|
||||
case .failure(let error):
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
locationManager.requestLocation()
|
||||
}
|
||||
}
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||
guard let location = locations.last else { return }
|
||||
locationContinuation?(.success(location.coordinate))
|
||||
locationContinuation = nil
|
||||
}
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
||||
locationContinuation?(.failure(error))
|
||||
locationContinuation = nil
|
||||
}
|
||||
}
|
||||
|
||||
private extension MainTabScanHandler {
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
//
|
||||
// OrderLocationProvider.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
|
||||
/// 一次性定位,用于多点位核销拉取打卡点。
|
||||
final class OrderLocationProvider: NSObject, CLLocationManagerDelegate {
|
||||
private let manager = CLLocationManager()
|
||||
private var continuation: ((Result<CLLocationCoordinate2D, Error>) -> Void)?
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
manager.delegate = self
|
||||
manager.desiredAccuracy = kCLLocationAccuracyBest
|
||||
}
|
||||
|
||||
func requestCoordinate() async throws -> CLLocationCoordinate2D {
|
||||
let status = manager.authorizationStatus
|
||||
if status == .notDetermined {
|
||||
manager.requestWhenInUseAuthorization()
|
||||
}
|
||||
guard status == .authorizedWhenInUse || status == .authorizedAlways || manager.authorizationStatus == .authorizedWhenInUse else {
|
||||
throw OrderLocationError.permissionDenied
|
||||
}
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
self.continuation = { result in
|
||||
continuation.resume(with: result)
|
||||
}
|
||||
manager.requestLocation()
|
||||
}
|
||||
}
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||
guard let location = locations.last else { return }
|
||||
continuation?(.success(location.coordinate))
|
||||
continuation = nil
|
||||
}
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
||||
continuation?(.failure(error))
|
||||
continuation = nil
|
||||
}
|
||||
}
|
||||
|
||||
enum OrderLocationError: LocalizedError {
|
||||
case permissionDenied
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .permissionDenied: "缺少定位权限,功能无法进行"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user