capacitor-baidu-location 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,646 @@
1
+ import Foundation
2
+ @_exported import BMKLocationKit
3
+ import CoreLocation
4
+ import Network
5
+
6
+ /**
7
+ * 百度地图定位插件iOS端实现类
8
+ * 负责处理百度地图定位的核心逻辑,包括定位权限管理、AK验证、定位请求等
9
+ */
10
+ @objc public class CPBaiduLocation: NSObject, BMKLocationAuthDelegate, BMKLocationManagerDelegate, CLLocationManagerDelegate {
11
+
12
+ /**
13
+ * 插件配置对象
14
+ */
15
+ private var config: CPBaiduLocationConfig
16
+
17
+ /**
18
+ * 系统定位管理器(用于权限请求和系统级定位)
19
+ */
20
+ private var clLocationManager: CLLocationManager = CLLocationManager()
21
+
22
+ /**
23
+ * 定位结果回调(弱引用自身避免循环引用)
24
+ */
25
+ private var completion: (([String: Any]) -> Void)?
26
+
27
+ /**
28
+ * 标记是否已执行回调(防止重复调用)
29
+ */
30
+ private var isCallbackInvoked = false
31
+
32
+ // MARK: - 状态管理
33
+
34
+ /**
35
+ * API Key
36
+ */
37
+ private var ak: String?
38
+
39
+ /**
40
+ * AK验证状态
41
+ */
42
+ private var isAKValidated = false
43
+
44
+ /**
45
+ * 坐标类型
46
+ * 默认值:BMKLocationCoordinateType.BMK09LL(百度经纬度坐标)
47
+ */
48
+ private var coordinateType: BMKLocationCoordinateType = .BMK09LL
49
+
50
+ /**
51
+ * AK验证回调队列
52
+ * 存储等待AK验证结果的回调
53
+ */
54
+ private var akValidationCallbacks: [(Bool, Int?, String?) -> Void] = []
55
+
56
+ /**
57
+ * 定位权限回调队列
58
+ * 存储等待定位权限结果的回调
59
+ */
60
+ private var locationPermissionCallbacks: [(String) -> Void] = []
61
+
62
+ // MARK: - 网络状态监听
63
+
64
+ /**
65
+ * 网络状态监视器
66
+ */
67
+ private var networkMonitor: NWPathMonitor?
68
+
69
+ /**
70
+ * 当前网络路径
71
+ */
72
+ private var networkPath: NWPath?
73
+
74
+ /**
75
+ * 是否已启动网络监听
76
+ */
77
+ private var isMonitoringNetwork = false
78
+
79
+ /**
80
+ * 百度定位管理器(持强引用避免提前释放)
81
+ * 懒加载,确保在需要时才初始化
82
+ */
83
+ private lazy var bmkLocationManager: BMKLocationManager = {
84
+ // 初始化并配置定位管理器
85
+ let manager = BMKLocationManager()
86
+ manager.delegate = self
87
+ manager.coordinateType = coordinateType // 坐标类型
88
+ manager.desiredAccuracy = kCLLocationAccuracyBest // 默认定位精度
89
+ manager.activityType = .automotiveNavigation // 默认定位场景
90
+ manager.pausesLocationUpdatesAutomatically = false // 默认不被系统自动暂停定位
91
+ manager.allowsBackgroundLocationUpdates = true // 允许后台定位,需要在xcode开启配置
92
+ manager.locationTimeout = 10 // 定位超时时间,单位:秒
93
+ manager.reGeocodeTimeout = 10 // 逆地理编码超时时间,单位:秒
94
+ return manager
95
+ }()
96
+
97
+ /**
98
+ * 使用配置对象初始化
99
+ * @param config 配置对象
100
+ */
101
+ public init(config: CPBaiduLocationConfig) {
102
+ self.config = config
103
+ super.init()
104
+ clLocationManager.delegate = self
105
+ }
106
+
107
+ /**
108
+ * 默认初始化
109
+ */
110
+ override init() {
111
+ self.config = CPBaiduLocationConfig(from: nil)
112
+ super.init()
113
+ clLocationManager.delegate = self
114
+ }
115
+
116
+
117
+
118
+ // MARK: - AK管理
119
+ /**
120
+ * 设置百度地图API Key
121
+ * @param ak API Key
122
+ * @param completion 验证结果回调
123
+ */
124
+ @objc public func setAK(_ ak: String, completion: @escaping ([String: Any]) -> Void) {
125
+ if isAKValidated || ak == self.ak {
126
+ completion([
127
+ "success": true,
128
+ ])
129
+ }
130
+ // 存储API Key
131
+ self.ak = ak
132
+
133
+ // 重置验证状态
134
+ isAKValidated = false
135
+
136
+ // 自动设置同意隐私协议
137
+ if !BMKLocationAuth.sharedInstance().isAgreePrivacy {
138
+ BMKLocationAuth.sharedInstance().setAgreePrivacy(true)
139
+ }
140
+
141
+ // 添加到回调队列
142
+ akValidationCallbacks.append { success, errorCode, errorMessage in
143
+ var result: [String: Any] = [
144
+ "success": success
145
+ ]
146
+
147
+ if let errorCode = errorCode {
148
+ result["errorCode"] = errorCode
149
+ }
150
+
151
+ if let errorMessage = errorMessage {
152
+ result["errorMessage"] = errorMessage
153
+ }
154
+
155
+ completion(result)
156
+ }
157
+
158
+ startNetworkMonitoring()
159
+
160
+ // 触发AK验证
161
+ BMKLocationAuth.sharedInstance().checkPermision(withKey: ak, authDelegate: self)
162
+ }
163
+
164
+ // MARK: - 坐标类型管理
165
+ /**
166
+ * 辅助方法:将字符串类型的坐标类型转换为BMKLocationCoordinateType枚举类型
167
+ * @param type 坐标类型字符串
168
+ * @return BMKLocationCoordinateType枚举类型
169
+ */
170
+ private func coordinateTypeFromString(_ type: String) -> BMKLocationCoordinateType {
171
+ switch type {
172
+ case "BD09LL":
173
+ return .BMK09LL // 百度经纬度坐标
174
+ case "BD09MC":
175
+ return .BMK09MC // 百度墨卡托米制坐标
176
+ case "GCJ02":
177
+ return .GCJ02 // 国测局坐标
178
+ case "WGS84":
179
+ return .WGS84 // WGS84坐标
180
+ default:
181
+ return .BMK09LL // 默认百度经纬度坐标
182
+ }
183
+ }
184
+
185
+ /**
186
+ * 设置坐标类型
187
+ * @param type 坐标类型
188
+ * @param completion 设置结果回调
189
+ */
190
+ @objc public func setCoordinateType(_ type: String, completion: @escaping () -> Void) {
191
+ // 根据坐标类型设置定位参数
192
+ self.coordinateType = coordinateTypeFromString(type)
193
+ if bmkLocationManager != nil {
194
+ bmkLocationManager.coordinateType = coordinateType
195
+ }
196
+ completion()
197
+ }
198
+
199
+ /**
200
+ * 获取当前坐标类型
201
+ * @return 当前坐标类型
202
+ */
203
+ @objc public func getCoordinateType() -> String {
204
+ switch coordinateType {
205
+ case .BMK09LL:
206
+ return "BD09LL"
207
+ case .BMK09MC:
208
+ return "BMK09MC"
209
+ case .GCJ02:
210
+ return "GCJ02"
211
+ case .WGS84:
212
+ return "WGS84"
213
+ default:
214
+ return "BD09LL"
215
+ }
216
+ }
217
+
218
+ // MARK: - 权限管理
219
+ /**
220
+ * 检查定位权限状态
221
+ * @return 权限状态结果
222
+ */
223
+ @objc public func checkPermission() -> [String: Any] {
224
+ let status = CLLocationManager.authorizationStatus()
225
+
226
+ var statusString: String
227
+ var hasPermission = false
228
+
229
+ switch status {
230
+ case .authorizedWhenInUse, .authorizedAlways:
231
+ statusString = "granted"
232
+ hasPermission = true
233
+ case .denied, .restricted:
234
+ statusString = "denied"
235
+ hasPermission = false
236
+ case .notDetermined:
237
+ statusString = "notDetermined"
238
+ hasPermission = false
239
+ @unknown default:
240
+ statusString = "unknown"
241
+ hasPermission = false
242
+ }
243
+
244
+ return [
245
+ "success": true,
246
+ "hasPermission": hasPermission,
247
+ "status": statusString
248
+ ]
249
+ }
250
+
251
+ /**
252
+ * 请求定位权限
253
+ * @param completion 权限请求结果回调
254
+ */
255
+ @objc public func requestPermission(completion: @escaping ([String: Any]) -> Void) {
256
+ let status = CLLocationManager.authorizationStatus()
257
+
258
+ if status == .notDetermined {
259
+ // 请求权限
260
+ locationPermissionCallbacks.append { statusString in
261
+ let hasPermission = statusString == "granted"
262
+ completion([
263
+ "success": true,
264
+ "granted": hasPermission,
265
+ "status": statusString
266
+ ])
267
+ }
268
+ clLocationManager.requestWhenInUseAuthorization()
269
+ } else {
270
+ // 直接返回当前状态
271
+ let checkResult = self.checkPermission()
272
+ completion([
273
+ "success": true,
274
+ "granted": checkResult["hasPermission"] as? Bool ?? false,
275
+ "status": checkResult["status"] as? String ?? "unknown"
276
+ ])
277
+ }
278
+ }
279
+
280
+ // MARK: - 定位
281
+ /**
282
+ * 获取当前位置信息
283
+ * @param needAddress 是否需要地址信息
284
+ * @param needLocationDescribe 是否需要位置描述
285
+ * @param completion 定位结果回调(主线程执行)
286
+ */
287
+ @objc public func getCurrentPosition(needAddress: Bool, needLocationDescribe: Bool, completion: @escaping ([String: Any]) -> Void) {
288
+ // 重置状态
289
+ isCallbackInvoked = false
290
+ self.completion = completion
291
+
292
+ // 检查前置条件
293
+ checkPreconditions { [weak self] isReady, errorCode, errorMessage in
294
+ guard let strongSelf = self else { return }
295
+
296
+ if !isReady {
297
+ // 前置条件不满足,返回错误
298
+ let result: [String: Any] = [
299
+ "latitude": 0.0,
300
+ "longitude": 0.0,
301
+ "accuracy": 0.0,
302
+ "errorCode": errorCode ?? -1,
303
+ "errorMessage": errorMessage ?? "定位前置条件不满足"
304
+ ]
305
+ strongSelf.invokeCompletion(with: result)
306
+ } else {
307
+ // 前置条件满足,执行定位
308
+ strongSelf.performLocationRequest(needAddress: needAddress, needLocationDescribe: needLocationDescribe)
309
+ }
310
+ }
311
+ }
312
+
313
+ /**
314
+ * 检查定位前置条件
315
+ * @param completion 检查结果回调
316
+ */
317
+ private func checkPreconditions(completion: @escaping (Bool, Int?, String?) -> Void) {
318
+ // 检查定位权限
319
+ let permissionResult = checkPermission()
320
+ if !(permissionResult["hasPermission"] as? Bool ?? false) {
321
+ completion(false, -3, "定位权限未授权!")
322
+ return
323
+ }
324
+
325
+ // 检查系统定位服务是否开启
326
+ if !CLLocationManager.locationServicesEnabled() {
327
+ completion(false, 2, "定位服务未开启!")
328
+ return
329
+ }
330
+
331
+ // 检查AK是否已验证
332
+ if isAKValidated {
333
+ completion(true, nil, nil)
334
+ return
335
+ } else {
336
+ completion(false, -1, "未设置AK!")
337
+ return
338
+ }
339
+ }
340
+
341
+ /**
342
+ * 执行定位请求
343
+ * @param needAddress 是否需要地址信息
344
+ * @param needLocationDescribe 是否需要位置描述
345
+ */
346
+ private func performLocationRequest(needAddress: Bool, needLocationDescribe: Bool) {
347
+ // 请求单次定位
348
+ bmkLocationManager.requestLocation(withReGeocode: needAddress, withNetworkState: true) { [weak self] (location, state, error) in
349
+ guard let strongSelf = self else { return }
350
+ // 构建定位结果
351
+ let result = strongSelf.buildLocationResult(
352
+ location: location,
353
+ error: error,
354
+ needAddress: needAddress,
355
+ needLocationDescribe: needLocationDescribe
356
+ )
357
+ // 执行回调(确保仅调用一次)
358
+ strongSelf.invokeCompletion(with: result)
359
+ }
360
+ }
361
+
362
+ // MARK: - 其他方法
363
+ /**
364
+ * 构建定位结果字典
365
+ * @param location 百度定位信息对象
366
+ * @param error 定位错误(如果有)
367
+ * @param needAddress 是否需要地址信息
368
+ * @param needLocationDescribe 是否需要位置描述
369
+ * @return 定位结果字典,包含经纬度、精度、地址信息等
370
+ */
371
+ private func buildLocationResult(
372
+ location: BMKLocation?,
373
+ error: Error?,
374
+ needAddress: Bool,
375
+ needLocationDescribe: Bool
376
+ ) -> [String: Any] {
377
+ // 初始化默认结果(兜底值)
378
+ var result: [String: Any] = [
379
+ "latitude": 0.0,
380
+ "longitude": 0.0,
381
+ "accuracy": 0.0,
382
+ "errorCode": -1,
383
+ "errorMessage": "定位失败",
384
+ "type": "baidu" // 标记为百度定位结果
385
+ ]
386
+
387
+ // 处理错误优先
388
+ if let error = error {
389
+ let nsError = error as NSError
390
+ let errorCode = nsError.code
391
+ result["errorCode"] = errorCode
392
+ result["errorMessage"] = nsError.localizedDescription
393
+
394
+ let bmkError = BMKLocationErrorCode(rawValue: nsError.code)
395
+ if (bmkError != nil) {
396
+ print("定位错误来自百度SDK")
397
+ }
398
+ return result
399
+ }
400
+
401
+ // 处理定位成功
402
+ guard let bmkLocation = location, let clLocation = bmkLocation.location else {
403
+ result["errorMessage"] = "未获取到位置信息"
404
+ return result
405
+ }
406
+
407
+ // 基础定位信息
408
+ result["latitude"] = clLocation.coordinate.latitude
409
+ result["longitude"] = clLocation.coordinate.longitude
410
+ result["accuracy"] = clLocation.horizontalAccuracy
411
+ result["errorCode"] = 0
412
+ result["errorMessage"] = "定位成功"
413
+
414
+ // 地址信息(按需添加)
415
+ if needAddress, let rgcData = bmkLocation.rgcData {
416
+ result["address"] = rgcData.address ?? ""
417
+ result["country"] = rgcData.country ?? ""
418
+ result["province"] = rgcData.province ?? ""
419
+ result["city"] = rgcData.city ?? ""
420
+ result["district"] = rgcData.district ?? ""
421
+ result["street"] = rgcData.street ?? ""
422
+ result["adcode"] = rgcData.adCode ?? ""
423
+ result["town"] = rgcData.town ?? ""
424
+ }
425
+
426
+ // 位置描述(按需添加)
427
+ if needLocationDescribe, let rgcData = bmkLocation.rgcData, let poiRegion = rgcData.poiRegion {
428
+ result["locationDescribe"] = "\(poiRegion.name ?? "") \(poiRegion.directionDesc ?? "")"
429
+ }
430
+
431
+ return result
432
+ }
433
+
434
+ /**
435
+ * 执行回调(确保主线程+仅执行一次)
436
+ * @param result 定位结果字典
437
+ */
438
+ private func invokeCompletion(with result: [String: Any]) {
439
+ guard !isCallbackInvoked else { return }
440
+ isCallbackInvoked = true
441
+
442
+ // 切主线程执行回调(避免UI操作崩溃)
443
+ DispatchQueue.main.async { [weak self] in
444
+ self?.completion?(result)
445
+ // 清理资源
446
+ self?.cleanup()
447
+ }
448
+ }
449
+
450
+ /**
451
+ * 清理定位资源
452
+ * 释放回调引用,避免循环引用
453
+ */
454
+ private func cleanup() {
455
+ completion = nil
456
+ }
457
+
458
+ // MARK: - 网络状态监听
459
+ /**
460
+ * 启动网络状态监听
461
+ */
462
+ private func startNetworkMonitoring() {
463
+ guard !isMonitoringNetwork else { return }
464
+
465
+ networkMonitor = NWPathMonitor()
466
+ networkPath = networkMonitor?.currentPath
467
+
468
+ if networkPath?.status == .satisfied {
469
+ return
470
+ }
471
+
472
+ networkMonitor?.pathUpdateHandler = { [weak self] path in
473
+ guard let strongSelf = self else { return }
474
+ strongSelf.networkPath = path
475
+
476
+ // 当网络从不可用变为可用,且AK未验证成功时,重新触发AK验证
477
+ if path.status == .satisfied && !strongSelf.isAKValidated, let ak = strongSelf.ak {
478
+ print("网络恢复,重新验证AK")
479
+ DispatchQueue.main.async {
480
+ BMKLocationAuth.sharedInstance().checkPermision(withKey: ak, authDelegate: strongSelf)
481
+ }
482
+ }
483
+ }
484
+
485
+ // 启动监听
486
+ let queue = DispatchQueue(label: "com.baidu.location.network")
487
+ networkMonitor?.start(queue: queue)
488
+ isMonitoringNetwork = true
489
+ print("网络状态监听已启动")
490
+ }
491
+
492
+ /**
493
+ * 停止网络状态监听
494
+ */
495
+ private func stopNetworkMonitoring() {
496
+ guard isMonitoringNetwork else { return }
497
+
498
+ networkMonitor?.cancel()
499
+ networkMonitor = nil
500
+ networkPath = nil
501
+ isMonitoringNetwork = false
502
+ print("网络状态监听已停止")
503
+ }
504
+
505
+ // MARK: - CLLocationManagerDelegate
506
+ /**
507
+ * 定位权限状态变化回调 (iOS 14-)
508
+ * @param manager 定位管理器
509
+ * @param status 权限状态
510
+ */
511
+ @objc public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
512
+ handleLocationManager(manager, didChangeAuthorization: status)
513
+ }
514
+
515
+ /**
516
+ * 定位权限状态变化回调 (iOS 14+ 推荐使用)
517
+ * @param manager 定位管理器
518
+ */
519
+ @available(iOS 14.0, *)
520
+ @objc public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
521
+ handleLocationManager(manager, didChangeAuthorization: manager.authorizationStatus)
522
+ }
523
+
524
+ // MARK: - 定位权限状态变化处理
525
+ private func handleLocationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
526
+ // 执行所有等待的权限回调
527
+ let callbacks = locationPermissionCallbacks
528
+ locationPermissionCallbacks.removeAll()
529
+
530
+ let statusString: String
531
+ switch status {
532
+ case .authorizedWhenInUse, .authorizedAlways:
533
+ statusString = "granted"
534
+ // 检查精确定位状态(iOS 14+)
535
+ if #available(iOS 14.0, *) {
536
+ let accuracyStatus = manager.accuracyAuthorization
537
+ if accuracyStatus == .reducedAccuracy {
538
+ // 精确定位未开启,引导用户跳转到系统设置
539
+ // showPreciseLocationSettingAlert()
540
+ }
541
+ }
542
+ case .denied, .restricted:
543
+ statusString = "denied"
544
+ case .notDetermined:
545
+ statusString = "notDetermined"
546
+ @unknown default:
547
+ statusString = "unknown"
548
+ }
549
+
550
+ // 执行回调
551
+ callbacks.forEach { $0(statusString) }
552
+ }
553
+
554
+ // MARK: - 请求精确定位权限(iOS 14+)
555
+ @available(iOS 14.0, *)
556
+ private func requestPreciseLocationAuthorization() {
557
+ // 检查当前精确定位权限
558
+ let accuracyStatus = clLocationManager.accuracyAuthorization
559
+ switch accuracyStatus {
560
+ case .fullAccuracy:
561
+ // 已授权精确定位:可启动百度定位
562
+ print("accuracyStatus: fullAccuracy")
563
+ case .reducedAccuracy:
564
+ // 仅大致定位:请求临时精确定位(需 Info.plist 配置 PurposeKey)
565
+ clLocationManager.requestTemporaryFullAccuracyAuthorization(withPurposeKey: "LocationTempUsage")
566
+ @unknown default:
567
+ print("accuracyStatus: \(accuracyStatus)")
568
+ }
569
+ }
570
+
571
+ // MARK: - BMKLocationAuthDelegate
572
+ /**
573
+ * AK验证状态变化回调
574
+ * @param iError 错误号 : 为0时验证通过,具体参加BMKLocationAuthErrorCode
575
+ */
576
+ public func onCheckPermissionState(_ iError: BMKLocationAuthErrorCode) {
577
+ var success = false
578
+ var errorCode = iError.rawValue
579
+ var errorMessage: String?
580
+
581
+ // 如果网络状态为未确定(用户还未选择网络权限),则不执行回调,等待网络状态变化后重试
582
+ if let networkPath = networkPath, networkPath.status != .satisfied {
583
+ return
584
+ }
585
+
586
+ switch iError {
587
+ case .success:
588
+ success = true
589
+ isAKValidated = true
590
+ errorMessage = "AK验证成功"
591
+ ak = nil
592
+ // 触发懒加载(有个bug,之后再初始化就无法定位成功)
593
+ print("allowsBackgroundLocationUpdates = \(bmkLocationManager.allowsBackgroundLocationUpdates)")
594
+ // 验证成功,停止网络监听
595
+ stopNetworkMonitoring()
596
+ case .networkFailed:
597
+ success = false
598
+ isAKValidated = false
599
+ errorMessage = "网络错误导致AK验证失败"
600
+ case .failed:
601
+ success = false
602
+ isAKValidated = false
603
+ errorMessage = "KEY非法导致AK验证失败"
604
+ default:
605
+ success = false
606
+ isAKValidated = false
607
+ errorMessage = "未知AK验证错误"
608
+ }
609
+
610
+ // 执行所有等待的回调
611
+ let callbacks = akValidationCallbacks
612
+ akValidationCallbacks.removeAll()
613
+ callbacks.forEach { $0(success, errorCode, errorMessage) }
614
+ }
615
+
616
+ // MARK: - BMKLocationManagerDelegate
617
+ /**
618
+ * 定位失败代理回调(系统级错误触发)
619
+ * @param manager 定位管理器
620
+ * @param error 错误信息
621
+ */
622
+ public func bmkLocationManager(_ manager: BMKLocationManager, didFailWithError error: Error?) {
623
+ // 使用统一的错误处理逻辑
624
+ let result = buildLocationResult(
625
+ location: nil,
626
+ error: error,
627
+ needAddress: false,
628
+ needLocationDescribe: false
629
+ )
630
+
631
+ // 执行回调(防止重复)
632
+ invokeCompletion(with: result)
633
+ }
634
+
635
+ public func bmkLocationManager(_ manager: BMKLocationManager, didChange status: CLAuthorizationStatus) {
636
+ }
637
+
638
+ public func bmkLocationManagerDidChangeAuthorization(_ manager: BMKLocationManager) {
639
+ }
640
+
641
+ deinit {
642
+ // 确保网络监听已停止
643
+ stopNetworkMonitoring()
644
+ print("CPBaiduLocation实例已销毁")
645
+ }
646
+ }
@@ -0,0 +1,38 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ /**
5
+ * 百度地图定位插件配置类
6
+ * 集中管理所有配置项,提供类型安全的访问方式
7
+ */
8
+ public class CPBaiduLocationConfig {
9
+
10
+ /**
11
+ * iOS平台百度地图API Key
12
+ */
13
+ private var _iOSAK: String?
14
+
15
+ // MARK: - Getters and Setters
16
+
17
+ /**
18
+ * iOS平台百度地图API Key
19
+ */
20
+ public var iOSAK: String? {
21
+ get {
22
+ return _iOSAK
23
+ }
24
+ set {
25
+ _iOSAK = newValue
26
+ }
27
+ }
28
+
29
+ /**
30
+ * 从Capacitor配置中初始化配置对象
31
+ */
32
+ convenience public init(from config: PluginConfig?) {
33
+ self.init()
34
+ if let config = config {
35
+ self.iOSAK = config.getString("iOSAK")
36
+ }
37
+ }
38
+ }