expo-gaode-map 2.2.38 → 2.2.39
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.
- package/android/build.gradle +6 -31
- package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapView.kt +35 -12
- package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapViewModule.kt +2 -0
- package/android/src/main/java/expo/modules/gaodemap/managers/UIManager.kt +8 -0
- package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerView.kt +48 -7
- package/build/ExpoGaodeMapOfflineModule.js +1 -1
- package/build/ExpoGaodeMapOfflineModule.js.map +1 -1
- package/build/ExpoGaodeMapView.js +2 -8
- package/build/ExpoGaodeMapView.js.map +1 -1
- package/build/components/AreaMaskOverlay.d.ts +1 -2
- package/build/components/AreaMaskOverlay.d.ts.map +1 -1
- package/build/components/AreaMaskOverlay.js +2 -1
- package/build/components/AreaMaskOverlay.js.map +1 -1
- package/build/components/FoldableMapView.d.ts +3 -2
- package/build/components/FoldableMapView.d.ts.map +1 -1
- package/build/components/FoldableMapView.js +2 -1
- package/build/components/FoldableMapView.js.map +1 -1
- package/build/components/MapUI.js +2 -1
- package/build/components/MapUI.js.map +1 -1
- package/build/components/RouteOverlay.d.ts +1 -2
- package/build/components/RouteOverlay.d.ts.map +1 -1
- package/build/components/RouteOverlay.js +5 -5
- package/build/components/RouteOverlay.js.map +1 -1
- package/build/components/overlays/Circle.d.ts +1 -1
- package/build/components/overlays/Circle.d.ts.map +1 -1
- package/build/components/overlays/Circle.js +2 -1
- package/build/components/overlays/Circle.js.map +1 -1
- package/build/components/overlays/Cluster.d.ts +1 -1
- package/build/components/overlays/Cluster.d.ts.map +1 -1
- package/build/components/overlays/Cluster.js +2 -1
- package/build/components/overlays/Cluster.js.map +1 -1
- package/build/components/overlays/HeatMap.d.ts +1 -1
- package/build/components/overlays/HeatMap.d.ts.map +1 -1
- package/build/components/overlays/HeatMap.js +2 -1
- package/build/components/overlays/HeatMap.js.map +1 -1
- package/build/components/overlays/Marker.d.ts +1 -1
- package/build/components/overlays/Marker.d.ts.map +1 -1
- package/build/components/overlays/Marker.js +8 -7
- package/build/components/overlays/Marker.js.map +1 -1
- package/build/components/overlays/MultiPoint.d.ts +1 -2
- package/build/components/overlays/MultiPoint.d.ts.map +1 -1
- package/build/components/overlays/MultiPoint.js +2 -1
- package/build/components/overlays/MultiPoint.js.map +1 -1
- package/build/components/overlays/Polygon.d.ts +1 -1
- package/build/components/overlays/Polygon.d.ts.map +1 -1
- package/build/components/overlays/Polygon.js +2 -1
- package/build/components/overlays/Polygon.js.map +1 -1
- package/build/components/overlays/Polyline.d.ts +1 -1
- package/build/components/overlays/Polyline.d.ts.map +1 -1
- package/build/components/overlays/Polyline.js +2 -1
- package/build/components/overlays/Polyline.js.map +1 -1
- package/build/module/geometry.d.ts.map +1 -1
- package/build/module/geometry.js +2 -1
- package/build/module/geometry.js.map +1 -1
- package/build/search/ExpoGaodeMapSearchModule.d.ts +1 -1
- package/build/search/ExpoGaodeMapSearchModule.d.ts.map +1 -1
- package/build/search/ExpoGaodeMapSearchModule.js.map +1 -1
- package/build/types/common.types.d.ts +1 -1
- package/build/types/common.types.d.ts.map +1 -1
- package/build/types/common.types.js.map +1 -1
- package/build/types/index.d.ts +1 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/index.js.map +1 -1
- package/build/types/map-view.types.d.ts +13 -2
- package/build/types/map-view.types.d.ts.map +1 -1
- package/build/types/map-view.types.js.map +1 -1
- package/build/utils/GeoUtils.d.ts +2 -2
- package/build/utils/GeoUtils.d.ts.map +1 -1
- package/build/utils/GeoUtils.js +3 -3
- package/build/utils/GeoUtils.js.map +1 -1
- package/build/utils/RouteUtils.d.ts.map +1 -1
- package/build/utils/RouteUtils.js +5 -2
- package/build/utils/RouteUtils.js.map +1 -1
- package/ios/ExpoGaodeMapView.swift +281 -15
- package/ios/ExpoGaodeMapViewModule.swift +5 -0
- package/ios/managers/UIManager.swift +3 -0
- package/ios/overlays/MarkerView.swift +205 -38
- package/package.json +9 -7
- package/plugin/build/withGaodeMap.js +3 -2
|
@@ -28,6 +28,12 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
28
28
|
var showsScale: Bool = true
|
|
29
29
|
/// 是否启用缩放手势
|
|
30
30
|
var isZoomEnabled: Bool = true
|
|
31
|
+
/// 缩放手势锚点:gesture 跟随手势中心点;center 固定地图中心点
|
|
32
|
+
var zoomGestureAnchor: String = "gesture" {
|
|
33
|
+
didSet {
|
|
34
|
+
applyZoomGestureSettings()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
31
37
|
/// 是否启用滚动手势
|
|
32
38
|
var isScrollEnabled: Bool = true
|
|
33
39
|
/// 是否启用旋转手势
|
|
@@ -96,6 +102,8 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
96
102
|
private var uiManager: UIManager!
|
|
97
103
|
/// 地图是否已加载完成
|
|
98
104
|
private var isMapLoaded = false
|
|
105
|
+
/// 定位 annotation 首次出现后是否已补应用定位样式
|
|
106
|
+
private var hasAppliedUserLocationStyleAfterLocationUpdate = false
|
|
99
107
|
/// 初始相机是否已应用(仅应用一次,避免与运行时相机控制冲突)
|
|
100
108
|
private var hasAppliedInitialCameraPosition = false
|
|
101
109
|
/// 是否正在处理 annotation 选择事件
|
|
@@ -106,6 +114,8 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
106
114
|
private var overlayContainer: UIView!
|
|
107
115
|
/// 显式跟踪所有覆盖物视图(新架构下 subviews 可能不可靠)
|
|
108
116
|
private var overlayViews: [UIView] = []
|
|
117
|
+
/// MarkerView 短暂 detach 时延迟注销,避免 annotation 暂时找不到自定义 view 而回退默认样式
|
|
118
|
+
private var pendingMarkerOverlayUnregisterTasks: [ObjectIdentifier: DispatchWorkItem] = [:]
|
|
109
119
|
|
|
110
120
|
// MARK: - 事件节流控制
|
|
111
121
|
|
|
@@ -113,9 +123,15 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
113
123
|
private var pendingCameraMoveData: [String: Any]?
|
|
114
124
|
private var cameraMoveDispatchWorkItem: DispatchWorkItem?
|
|
115
125
|
private var lastCameraMoveDispatchTime: CFTimeInterval = 0
|
|
126
|
+
/// iOS 缩放惯性/连续 setZoom 可能多次触发 regionDidChange,合并后再派发 idle。
|
|
127
|
+
private var pendingCameraIdleData: [String: Any]?
|
|
128
|
+
private var cameraIdleDispatchWorkItem: DispatchWorkItem?
|
|
129
|
+
private let cameraIdleDebounceMs = 160
|
|
116
130
|
|
|
117
131
|
/// 缩放手势识别器(用于模拟惯性)
|
|
118
132
|
private var pinchGesture: UIPinchGestureRecognizer!
|
|
133
|
+
private var centerAnchoredPinchStartZoom: CGFloat?
|
|
134
|
+
private var centerAnchoredZoomCoordinate: CLLocationCoordinate2D?
|
|
119
135
|
|
|
120
136
|
// 惯性动画相关属性
|
|
121
137
|
private var displayLink: CADisplayLink?
|
|
@@ -237,6 +253,8 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
237
253
|
}
|
|
238
254
|
|
|
239
255
|
private func registerOverlayView(_ view: UIView) {
|
|
256
|
+
cancelPendingMarkerOverlayUnregister(for: view)
|
|
257
|
+
|
|
240
258
|
guard !overlayViews.contains(where: { $0 === view }) else {
|
|
241
259
|
return
|
|
242
260
|
}
|
|
@@ -244,9 +262,31 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
244
262
|
}
|
|
245
263
|
|
|
246
264
|
private func unregisterOverlayView(_ view: UIView) {
|
|
265
|
+
cancelPendingMarkerOverlayUnregister(for: view)
|
|
247
266
|
overlayViews.removeAll { $0 === view }
|
|
248
267
|
}
|
|
249
268
|
|
|
269
|
+
private func scheduleMarkerOverlayUnregister(_ markerView: MarkerView) {
|
|
270
|
+
cancelPendingMarkerOverlayUnregister(for: markerView)
|
|
271
|
+
|
|
272
|
+
let identifier = ObjectIdentifier(markerView)
|
|
273
|
+
let task = DispatchWorkItem { [weak self, weak markerView] in
|
|
274
|
+
guard let self = self, let markerView = markerView else { return }
|
|
275
|
+
self.pendingMarkerOverlayUnregisterTasks[identifier] = nil
|
|
276
|
+
guard markerView.superview == nil else { return }
|
|
277
|
+
self.unregisterOverlayView(markerView)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
pendingMarkerOverlayUnregisterTasks[identifier] = task
|
|
281
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: task)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private func cancelPendingMarkerOverlayUnregister(for view: UIView) {
|
|
285
|
+
let identifier = ObjectIdentifier(view)
|
|
286
|
+
pendingMarkerOverlayUnregisterTasks[identifier]?.cancel()
|
|
287
|
+
pendingMarkerOverlayUnregisterTasks[identifier] = nil
|
|
288
|
+
}
|
|
289
|
+
|
|
250
290
|
private func prepareOverlayViewForHosting(_ view: UIView) {
|
|
251
291
|
guard !(view is MarkerView) else {
|
|
252
292
|
return
|
|
@@ -322,8 +362,8 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
322
362
|
// 🔑 处理所有覆盖物 - 从跟踪数组中移除并确保 native 对象也从地图移除
|
|
323
363
|
// 🔑 关键修复:先从数组移除,再调用 super,防止 super 触发的事件回调中引用已卸载的视图
|
|
324
364
|
if let markerView = subview as? MarkerView {
|
|
325
|
-
|
|
326
|
-
// MarkerView
|
|
365
|
+
scheduleMarkerOverlayUnregister(markerView)
|
|
366
|
+
// MarkerView 内部也会延迟确认 annotation 移除,避免 transient detach 闪烁。
|
|
327
367
|
} else if let circleView = subview as? CircleView {
|
|
328
368
|
unregisterOverlayView(circleView)
|
|
329
369
|
if let mapView, let circle = circleView.circle {
|
|
@@ -361,12 +401,12 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
361
401
|
uiManager.setMapType(1)
|
|
362
402
|
uiManager.setShowsScale(showsScale)
|
|
363
403
|
uiManager.setShowsCompass(showsCompass)
|
|
364
|
-
|
|
404
|
+
applyZoomGestureSettings()
|
|
365
405
|
uiManager.setScrollEnabled(isScrollEnabled)
|
|
366
406
|
uiManager.setRotateEnabled(isRotateEnabled)
|
|
367
407
|
uiManager.setTiltEnabled(isTiltEnabled)
|
|
368
408
|
uiManager.setShowsUserLocation(showsUserLocation, followUser: followUserLocation)
|
|
369
|
-
|
|
409
|
+
applyUserLocationStyle()
|
|
370
410
|
}
|
|
371
411
|
|
|
372
412
|
/**
|
|
@@ -390,21 +430,21 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
390
430
|
|
|
391
431
|
uiManager.setShowsScale(showsScale)
|
|
392
432
|
uiManager.setShowsCompass(showsCompass)
|
|
393
|
-
|
|
433
|
+
applyZoomGestureSettings()
|
|
394
434
|
uiManager.setScrollEnabled(isScrollEnabled)
|
|
395
435
|
uiManager.setRotateEnabled(isRotateEnabled)
|
|
396
436
|
uiManager.setTiltEnabled(isTiltEnabled)
|
|
397
437
|
uiManager.setShowsUserLocation(showsUserLocation, followUser: followUserLocation)
|
|
438
|
+
applyUserLocationStyle()
|
|
398
439
|
uiManager.setShowsTraffic(showsTraffic)
|
|
399
440
|
uiManager.setShowsBuildings(showsBuildings)
|
|
400
441
|
uiManager.setShowsIndoorMap(showsIndoorMap)
|
|
401
|
-
updatePinchGestureState()
|
|
402
442
|
mapView.distanceFilter = distanceFilter
|
|
403
443
|
mapView.headingFilter = headingFilter
|
|
404
444
|
if let customMapStyleData {
|
|
405
445
|
uiManager.setCustomMapStyle(customMapStyleData)
|
|
406
446
|
}
|
|
407
|
-
|
|
447
|
+
|
|
408
448
|
// 更新苹果地图样式
|
|
409
449
|
updateAppleMapStyle()
|
|
410
450
|
|
|
@@ -439,6 +479,13 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
439
479
|
// MARK: - 手势处理
|
|
440
480
|
|
|
441
481
|
@objc func handlePinch(_ gesture: UIPinchGestureRecognizer) {
|
|
482
|
+
guard isZoomEnabled else { return }
|
|
483
|
+
|
|
484
|
+
if zoomGestureAnchor == "center" {
|
|
485
|
+
handleCenterAnchoredPinch(gesture)
|
|
486
|
+
return
|
|
487
|
+
}
|
|
488
|
+
|
|
442
489
|
if gesture.state == .began {
|
|
443
490
|
// 手势开始,立即停止之前的惯性动画,避免冲突
|
|
444
491
|
stopInertiaAnimation()
|
|
@@ -455,6 +502,44 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
455
502
|
}
|
|
456
503
|
}
|
|
457
504
|
}
|
|
505
|
+
|
|
506
|
+
private func handleCenterAnchoredPinch(_ gesture: UIPinchGestureRecognizer) {
|
|
507
|
+
switch gesture.state {
|
|
508
|
+
case .began:
|
|
509
|
+
stopInertiaAnimation()
|
|
510
|
+
centerAnchoredPinchStartZoom = mapView.zoomLevel
|
|
511
|
+
centerAnchoredZoomCoordinate = mapView.centerCoordinate
|
|
512
|
+
case .changed:
|
|
513
|
+
let startZoom = centerAnchoredPinchStartZoom ?? mapView.zoomLevel
|
|
514
|
+
let center = centerAnchoredZoomCoordinate ?? mapView.centerCoordinate
|
|
515
|
+
let zoomDelta = log2(Double(max(gesture.scale, 0.0001)))
|
|
516
|
+
applyCenterAnchoredZoom(CGFloat(Double(startZoom) + zoomDelta), center: center)
|
|
517
|
+
case .ended, .cancelled, .failed:
|
|
518
|
+
centerAnchoredPinchStartZoom = nil
|
|
519
|
+
let velocity = gesture.velocity
|
|
520
|
+
if abs(velocity) > 0.1 {
|
|
521
|
+
zoomVelocity = Double(velocity) * 0.02
|
|
522
|
+
if centerAnchoredZoomCoordinate == nil {
|
|
523
|
+
centerAnchoredZoomCoordinate = mapView.centerCoordinate
|
|
524
|
+
}
|
|
525
|
+
startInertiaAnimation()
|
|
526
|
+
} else {
|
|
527
|
+
centerAnchoredZoomCoordinate = nil
|
|
528
|
+
}
|
|
529
|
+
default:
|
|
530
|
+
break
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
private func applyCenterAnchoredZoom(_ zoom: CGFloat, center: CLLocationCoordinate2D) {
|
|
535
|
+
let validZoom = max(mapView.minZoomLevel, min(mapView.maxZoomLevel, zoom))
|
|
536
|
+
let status = MAMapStatus()
|
|
537
|
+
status.centerCoordinate = center
|
|
538
|
+
status.zoomLevel = validZoom
|
|
539
|
+
status.rotationDegree = mapView.rotationDegree
|
|
540
|
+
status.cameraDegree = mapView.cameraDegree
|
|
541
|
+
mapView.setMapStatus(status, animated: false, duration: 0)
|
|
542
|
+
}
|
|
458
543
|
|
|
459
544
|
private func startInertiaAnimation() {
|
|
460
545
|
stopInertiaAnimation()
|
|
@@ -476,12 +561,21 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
476
561
|
// 碰到边界,停止动画
|
|
477
562
|
newZoom = max(mapView.minZoomLevel, min(mapView.maxZoomLevel, newZoom))
|
|
478
563
|
stopInertiaAnimation()
|
|
479
|
-
|
|
564
|
+
if zoomGestureAnchor == "center" {
|
|
565
|
+
applyCenterAnchoredZoom(newZoom, center: centerAnchoredZoomCoordinate ?? mapView.centerCoordinate)
|
|
566
|
+
centerAnchoredZoomCoordinate = nil
|
|
567
|
+
} else {
|
|
568
|
+
mapView.setZoomLevel(newZoom, animated: false)
|
|
569
|
+
}
|
|
480
570
|
return
|
|
481
571
|
}
|
|
482
572
|
|
|
483
573
|
// 更新地图缩放级别(animated: false 以保证逐帧控制的流畅性)
|
|
484
|
-
|
|
574
|
+
if zoomGestureAnchor == "center" {
|
|
575
|
+
applyCenterAnchoredZoom(newZoom, center: centerAnchoredZoomCoordinate ?? mapView.centerCoordinate)
|
|
576
|
+
} else {
|
|
577
|
+
mapView.setZoomLevel(newZoom, animated: false)
|
|
578
|
+
}
|
|
485
579
|
|
|
486
580
|
// 减速(应用摩擦力)
|
|
487
581
|
zoomVelocity *= friction
|
|
@@ -489,6 +583,7 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
489
583
|
// 停止条件
|
|
490
584
|
if abs(zoomVelocity) < velocityThreshold {
|
|
491
585
|
stopInertiaAnimation()
|
|
586
|
+
centerAnchoredZoomCoordinate = nil
|
|
492
587
|
}
|
|
493
588
|
}
|
|
494
589
|
|
|
@@ -568,6 +663,9 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
568
663
|
func setFollowUserLocation(_ follow: Bool) {
|
|
569
664
|
followUserLocation = follow
|
|
570
665
|
uiManager?.setShowsUserLocation(showsUserLocation, followUser: follow)
|
|
666
|
+
if showsUserLocation {
|
|
667
|
+
applyUserLocationStyle()
|
|
668
|
+
}
|
|
571
669
|
}
|
|
572
670
|
|
|
573
671
|
func setShowsUserLocation(_ show: Bool) {
|
|
@@ -580,6 +678,7 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
580
678
|
|
|
581
679
|
func setUserLocationRepresentation(_ config: [String: Any]) {
|
|
582
680
|
userLocationRepresentation = config
|
|
681
|
+
hasAppliedUserLocationStyleAfterLocationUpdate = false
|
|
583
682
|
if showsUserLocation {
|
|
584
683
|
uiManager?.setUserLocationRepresentation(config)
|
|
585
684
|
}
|
|
@@ -589,7 +688,7 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
589
688
|
* 应用用户位置样式
|
|
590
689
|
*/
|
|
591
690
|
private func applyUserLocationStyle() {
|
|
592
|
-
guard let config = userLocationRepresentation else { return }
|
|
691
|
+
guard showsUserLocation, let config = userLocationRepresentation else { return }
|
|
593
692
|
uiManager?.setUserLocationRepresentation(config)
|
|
594
693
|
}
|
|
595
694
|
|
|
@@ -806,6 +905,9 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
806
905
|
pendingCameraMoveData = nil
|
|
807
906
|
cameraMoveDispatchWorkItem?.cancel()
|
|
808
907
|
cameraMoveDispatchWorkItem = nil
|
|
908
|
+
pendingCameraIdleData = nil
|
|
909
|
+
cameraIdleDispatchWorkItem?.cancel()
|
|
910
|
+
cameraIdleDispatchWorkItem = nil
|
|
809
911
|
if let privacyObserver {
|
|
810
912
|
NotificationCenter.default.removeObserver(privacyObserver)
|
|
811
913
|
}
|
|
@@ -822,6 +924,8 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
822
924
|
appleMapView?.removeOverlays(appleMapView?.overlays ?? [])
|
|
823
925
|
|
|
824
926
|
// 清空覆盖物数组
|
|
927
|
+
pendingMarkerOverlayUnregisterTasks.values.forEach { $0.cancel() }
|
|
928
|
+
pendingMarkerOverlayUnregisterTasks.removeAll()
|
|
825
929
|
overlayViews.removeAll()
|
|
826
930
|
|
|
827
931
|
// 移除所有子视图
|
|
@@ -861,6 +965,7 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
861
965
|
super.addSubview(resolvedMapView)
|
|
862
966
|
isMapLoaded = false
|
|
863
967
|
hasAppliedInitialCameraPosition = false
|
|
968
|
+
hasAppliedUserLocationStyleAfterLocationUpdate = false
|
|
864
969
|
|
|
865
970
|
cameraManager = CameraManager(mapView: resolvedMapView)
|
|
866
971
|
uiManager = UIManager(mapView: resolvedMapView)
|
|
@@ -879,13 +984,20 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
879
984
|
pinchGesture.delegate = self
|
|
880
985
|
resolvedMapView.addGestureRecognizer(pinchGesture)
|
|
881
986
|
self.pinchGesture = pinchGesture
|
|
882
|
-
|
|
987
|
+
applyZoomGestureSettings()
|
|
883
988
|
}
|
|
884
989
|
|
|
885
|
-
private func
|
|
990
|
+
private func applyZoomGestureSettings() {
|
|
991
|
+
uiManager?.setZoomEnabled(isZoomEnabled && zoomGestureAnchor != "center")
|
|
886
992
|
pinchGesture?.isEnabled = isZoomEnabled
|
|
993
|
+
if zoomGestureAnchor != "center" {
|
|
994
|
+
centerAnchoredPinchStartZoom = nil
|
|
995
|
+
centerAnchoredZoomCoordinate = nil
|
|
996
|
+
}
|
|
887
997
|
if !isZoomEnabled {
|
|
888
998
|
stopInertiaAnimation()
|
|
999
|
+
centerAnchoredPinchStartZoom = nil
|
|
1000
|
+
centerAnchoredZoomCoordinate = nil
|
|
889
1001
|
}
|
|
890
1002
|
}
|
|
891
1003
|
}
|
|
@@ -937,9 +1049,37 @@ extension ExpoGaodeMapView {
|
|
|
937
1049
|
if let styleData = customMapStyleData {
|
|
938
1050
|
uiManager?.setCustomMapStyle(styleData)
|
|
939
1051
|
}
|
|
1052
|
+
|
|
1053
|
+
applyUserLocationStyle()
|
|
940
1054
|
|
|
941
1055
|
onLoad(["loaded": true])
|
|
942
1056
|
}
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* 定位更新回调
|
|
1060
|
+
*/
|
|
1061
|
+
public func mapView(_ mapView: MAMapView, didUpdate userLocation: MAUserLocation, updatingLocation: Bool) {
|
|
1062
|
+
guard updatingLocation, let location = userLocation.location else { return }
|
|
1063
|
+
|
|
1064
|
+
if !hasAppliedUserLocationStyleAfterLocationUpdate {
|
|
1065
|
+
applyUserLocationStyle()
|
|
1066
|
+
hasAppliedUserLocationStyleAfterLocationUpdate = true
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
let latitude = location.coordinate.latitude
|
|
1070
|
+
let longitude = location.coordinate.longitude
|
|
1071
|
+
guard latitude >= -90 && latitude <= 90,
|
|
1072
|
+
longitude >= -180 && longitude <= 180 else {
|
|
1073
|
+
return
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
onLocation([
|
|
1077
|
+
"latitude": latitude,
|
|
1078
|
+
"longitude": longitude,
|
|
1079
|
+
"accuracy": location.horizontalAccuracy,
|
|
1080
|
+
"timestamp": Date().timeIntervalSince1970 * 1000
|
|
1081
|
+
])
|
|
1082
|
+
}
|
|
943
1083
|
|
|
944
1084
|
/**
|
|
945
1085
|
* 地图区域即将改变时触发
|
|
@@ -958,7 +1098,7 @@ extension ExpoGaodeMapView {
|
|
|
958
1098
|
*/
|
|
959
1099
|
public func mapView(_ mapView: MAMapView, regionDidChangeAnimated animated: Bool) {
|
|
960
1100
|
flushPendingCameraMoveEvent()
|
|
961
|
-
|
|
1101
|
+
scheduleCameraIdleEvent(buildCameraEventData(for: mapView))
|
|
962
1102
|
|
|
963
1103
|
// 这里的 overlayViews 是 [UIView] 类型,可能包含 ClusterView
|
|
964
1104
|
for view in overlayViews {
|
|
@@ -1038,6 +1178,27 @@ extension ExpoGaodeMapView {
|
|
|
1038
1178
|
lastCameraMoveDispatchTime = timestamp
|
|
1039
1179
|
onCameraMove(eventData)
|
|
1040
1180
|
}
|
|
1181
|
+
|
|
1182
|
+
private func scheduleCameraIdleEvent(_ eventData: [String: Any]) {
|
|
1183
|
+
pendingCameraIdleData = eventData
|
|
1184
|
+
cameraIdleDispatchWorkItem?.cancel()
|
|
1185
|
+
|
|
1186
|
+
let workItem = DispatchWorkItem { [weak self] in
|
|
1187
|
+
guard let self = self, let latestEventData = self.pendingCameraIdleData else {
|
|
1188
|
+
return
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
self.pendingCameraIdleData = nil
|
|
1192
|
+
self.cameraIdleDispatchWorkItem = nil
|
|
1193
|
+
self.onCameraIdle(latestEventData)
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
cameraIdleDispatchWorkItem = workItem
|
|
1197
|
+
DispatchQueue.main.asyncAfter(
|
|
1198
|
+
deadline: .now() + Double(cameraIdleDebounceMs) / 1000.0,
|
|
1199
|
+
execute: workItem
|
|
1200
|
+
)
|
|
1201
|
+
}
|
|
1041
1202
|
|
|
1042
1203
|
/**
|
|
1043
1204
|
* 地图单击事件
|
|
@@ -1262,13 +1423,118 @@ extension ExpoGaodeMapView {
|
|
|
1262
1423
|
handleGaodePoiTouch(pois)
|
|
1263
1424
|
}
|
|
1264
1425
|
|
|
1426
|
+
private func getUserLocationAnnotationView(for mapView: MAMapView, annotation: MAAnnotation) -> MAAnnotationView? {
|
|
1427
|
+
guard let config = userLocationRepresentation,
|
|
1428
|
+
let imagePath = config["image"] as? String,
|
|
1429
|
+
!imagePath.isEmpty else {
|
|
1430
|
+
return nil
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
let reuseId = "ExpoGaodeMapUserLocationStyleReuseIdentifier"
|
|
1434
|
+
let annotationView: MAAnnotationView
|
|
1435
|
+
if let reusableView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) {
|
|
1436
|
+
annotationView = reusableView
|
|
1437
|
+
} else {
|
|
1438
|
+
annotationView = MAAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
|
|
1439
|
+
}
|
|
1440
|
+
annotationView.annotation = annotation
|
|
1441
|
+
annotationView.canShowCallout = false
|
|
1442
|
+
|
|
1443
|
+
let imageWidth = config["imageWidth"] as? Double
|
|
1444
|
+
let imageHeight = config["imageHeight"] as? Double
|
|
1445
|
+
let cacheKey = userLocationImageCacheKey(imagePath: imagePath, imageWidth: imageWidth, imageHeight: imageHeight)
|
|
1446
|
+
annotationView.accessibilityIdentifier = cacheKey
|
|
1447
|
+
|
|
1448
|
+
if let cached = IconBitmapCache.shared.image(forKey: cacheKey) {
|
|
1449
|
+
annotationView.image = cached
|
|
1450
|
+
return annotationView
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
annotationView.image = nil
|
|
1454
|
+
loadUserLocationImage(imagePath: imagePath, imageWidth: imageWidth, imageHeight: imageHeight, cacheKey: cacheKey) { [weak annotationView] image in
|
|
1455
|
+
guard let annotationView = annotationView,
|
|
1456
|
+
annotationView.accessibilityIdentifier == cacheKey else {
|
|
1457
|
+
return
|
|
1458
|
+
}
|
|
1459
|
+
annotationView.image = image
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
return annotationView
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
private func userLocationImageCacheKey(imagePath: String, imageWidth: Double?, imageHeight: Double?) -> String {
|
|
1466
|
+
let widthPart = imageWidth.map { String(Int($0.rounded())) } ?? "auto"
|
|
1467
|
+
let heightPart = imageHeight.map { String(Int($0.rounded())) } ?? "auto"
|
|
1468
|
+
return "userLocation|\(imagePath)|\(widthPart)x\(heightPart)"
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
private func loadUserLocationImage(
|
|
1472
|
+
imagePath: String,
|
|
1473
|
+
imageWidth: Double?,
|
|
1474
|
+
imageHeight: Double?,
|
|
1475
|
+
cacheKey: String,
|
|
1476
|
+
completion: @escaping (UIImage?) -> Void
|
|
1477
|
+
) {
|
|
1478
|
+
let finish: (UIImage?) -> Void = { image in
|
|
1479
|
+
if let image = image {
|
|
1480
|
+
IconBitmapCache.shared.setImage(image, forKey: cacheKey)
|
|
1481
|
+
}
|
|
1482
|
+
DispatchQueue.main.async {
|
|
1483
|
+
completion(image)
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
if imagePath.hasPrefix("http://") || imagePath.hasPrefix("https://") {
|
|
1488
|
+
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
1489
|
+
guard let self = self,
|
|
1490
|
+
let url = URL(string: imagePath),
|
|
1491
|
+
let data = try? Data(contentsOf: url),
|
|
1492
|
+
let image = UIImage(data: data) else {
|
|
1493
|
+
finish(nil)
|
|
1494
|
+
return
|
|
1495
|
+
}
|
|
1496
|
+
finish(self.resizeUserLocationImage(image, imageWidth: imageWidth, imageHeight: imageHeight))
|
|
1497
|
+
}
|
|
1498
|
+
return
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
let image: UIImage?
|
|
1502
|
+
if imagePath.hasPrefix("file://") {
|
|
1503
|
+
let path = String(imagePath.dropFirst(7))
|
|
1504
|
+
image = UIImage(contentsOfFile: path)
|
|
1505
|
+
} else {
|
|
1506
|
+
image = UIImage(named: imagePath)
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
guard let image = image else {
|
|
1510
|
+
finish(nil)
|
|
1511
|
+
return
|
|
1512
|
+
}
|
|
1513
|
+
finish(resizeUserLocationImage(image, imageWidth: imageWidth, imageHeight: imageHeight))
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
private func resizeUserLocationImage(_ image: UIImage, imageWidth: Double?, imageHeight: Double?) -> UIImage {
|
|
1517
|
+
guard let imageWidth = imageWidth,
|
|
1518
|
+
let imageHeight = imageHeight,
|
|
1519
|
+
imageWidth > 0,
|
|
1520
|
+
imageHeight > 0 else {
|
|
1521
|
+
return image
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
let targetSize = CGSize(width: imageWidth, height: imageHeight)
|
|
1525
|
+
let renderer = UIGraphicsImageRenderer(size: targetSize)
|
|
1526
|
+
return renderer.image { _ in
|
|
1527
|
+
image.draw(in: CGRect(origin: .zero, size: targetSize))
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1265
1531
|
/**
|
|
1266
1532
|
* 创建标注视图
|
|
1267
|
-
*
|
|
1533
|
+
* 定位蓝点默认返回 nil 使用系统样式;传入 image 时按官方建议自定义定位 annotationView。
|
|
1268
1534
|
*/
|
|
1269
1535
|
public func mapView(_ mapView: MAMapView, viewFor annotation: MAAnnotation) -> MAAnnotationView? {
|
|
1270
1536
|
if annotation.isKind(of: MAUserLocation.self) {
|
|
1271
|
-
return
|
|
1537
|
+
return getUserLocationAnnotationView(for: mapView, annotation: annotation)
|
|
1272
1538
|
}
|
|
1273
1539
|
|
|
1274
1540
|
// 🔑 支持 MAAnimatedAnnotation(平滑移动)
|
|
@@ -43,6 +43,11 @@ public class ExpoGaodeMapViewModule: Module {
|
|
|
43
43
|
Prop("zoomGesturesEnabled") { (view: ExpoGaodeMapView, enabled: Bool) in
|
|
44
44
|
view.isZoomEnabled = enabled
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
// 缩放手势锚点:gesture 使用手势焦点,center 固定地图中心点
|
|
48
|
+
Prop("zoomGestureAnchor") { (view: ExpoGaodeMapView, anchor: String?) in
|
|
49
|
+
view.zoomGestureAnchor = anchor == "center" ? "center" : "gesture"
|
|
50
|
+
}
|
|
46
51
|
|
|
47
52
|
Prop("scrollGesturesEnabled") { (view: ExpoGaodeMapView, enabled: Bool) in
|
|
48
53
|
view.isScrollEnabled = enabled
|
|
@@ -172,6 +172,9 @@ class UIManager: NSObject, MAMapViewDelegate {
|
|
|
172
172
|
// 是否显示方向指示
|
|
173
173
|
if let showsHeadingIndicator = config["showsHeadingIndicator"] as? Bool {
|
|
174
174
|
representation.showsHeadingIndicator = showsHeadingIndicator
|
|
175
|
+
if showsHeadingIndicator {
|
|
176
|
+
mapView.userTrackingMode = .followWithHeading
|
|
177
|
+
}
|
|
175
178
|
}
|
|
176
179
|
|
|
177
180
|
// 精度圈填充颜色
|