expo-gaode-map 2.2.38 → 2.2.40-next.0

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.
Files changed (79) hide show
  1. package/android/build.gradle +6 -31
  2. package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapView.kt +35 -12
  3. package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapViewModule.kt +2 -0
  4. package/android/src/main/java/expo/modules/gaodemap/managers/UIManager.kt +8 -0
  5. package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerView.kt +48 -7
  6. package/build/ExpoGaodeMapOfflineModule.js +1 -1
  7. package/build/ExpoGaodeMapOfflineModule.js.map +1 -1
  8. package/build/ExpoGaodeMapView.js +2 -8
  9. package/build/ExpoGaodeMapView.js.map +1 -1
  10. package/build/components/AreaMaskOverlay.d.ts +1 -2
  11. package/build/components/AreaMaskOverlay.d.ts.map +1 -1
  12. package/build/components/AreaMaskOverlay.js +2 -1
  13. package/build/components/AreaMaskOverlay.js.map +1 -1
  14. package/build/components/FoldableMapView.d.ts +3 -2
  15. package/build/components/FoldableMapView.d.ts.map +1 -1
  16. package/build/components/FoldableMapView.js +2 -1
  17. package/build/components/FoldableMapView.js.map +1 -1
  18. package/build/components/MapUI.js +2 -1
  19. package/build/components/MapUI.js.map +1 -1
  20. package/build/components/RouteOverlay.d.ts +1 -2
  21. package/build/components/RouteOverlay.d.ts.map +1 -1
  22. package/build/components/RouteOverlay.js +5 -5
  23. package/build/components/RouteOverlay.js.map +1 -1
  24. package/build/components/overlays/Circle.d.ts +1 -1
  25. package/build/components/overlays/Circle.d.ts.map +1 -1
  26. package/build/components/overlays/Circle.js +2 -1
  27. package/build/components/overlays/Circle.js.map +1 -1
  28. package/build/components/overlays/Cluster.d.ts +1 -1
  29. package/build/components/overlays/Cluster.d.ts.map +1 -1
  30. package/build/components/overlays/Cluster.js +2 -1
  31. package/build/components/overlays/Cluster.js.map +1 -1
  32. package/build/components/overlays/HeatMap.d.ts +1 -1
  33. package/build/components/overlays/HeatMap.d.ts.map +1 -1
  34. package/build/components/overlays/HeatMap.js +2 -1
  35. package/build/components/overlays/HeatMap.js.map +1 -1
  36. package/build/components/overlays/Marker.d.ts +1 -1
  37. package/build/components/overlays/Marker.d.ts.map +1 -1
  38. package/build/components/overlays/Marker.js +8 -7
  39. package/build/components/overlays/Marker.js.map +1 -1
  40. package/build/components/overlays/MultiPoint.d.ts +1 -2
  41. package/build/components/overlays/MultiPoint.d.ts.map +1 -1
  42. package/build/components/overlays/MultiPoint.js +2 -1
  43. package/build/components/overlays/MultiPoint.js.map +1 -1
  44. package/build/components/overlays/Polygon.d.ts +1 -1
  45. package/build/components/overlays/Polygon.d.ts.map +1 -1
  46. package/build/components/overlays/Polygon.js +2 -1
  47. package/build/components/overlays/Polygon.js.map +1 -1
  48. package/build/components/overlays/Polyline.d.ts +1 -1
  49. package/build/components/overlays/Polyline.d.ts.map +1 -1
  50. package/build/components/overlays/Polyline.js +2 -1
  51. package/build/components/overlays/Polyline.js.map +1 -1
  52. package/build/module/geometry.d.ts.map +1 -1
  53. package/build/module/geometry.js +2 -1
  54. package/build/module/geometry.js.map +1 -1
  55. package/build/search/ExpoGaodeMapSearchModule.d.ts +1 -1
  56. package/build/search/ExpoGaodeMapSearchModule.d.ts.map +1 -1
  57. package/build/search/ExpoGaodeMapSearchModule.js.map +1 -1
  58. package/build/types/common.types.d.ts +1 -1
  59. package/build/types/common.types.d.ts.map +1 -1
  60. package/build/types/common.types.js.map +1 -1
  61. package/build/types/index.d.ts +1 -1
  62. package/build/types/index.d.ts.map +1 -1
  63. package/build/types/index.js.map +1 -1
  64. package/build/types/map-view.types.d.ts +13 -2
  65. package/build/types/map-view.types.d.ts.map +1 -1
  66. package/build/types/map-view.types.js.map +1 -1
  67. package/build/utils/GeoUtils.d.ts +2 -2
  68. package/build/utils/GeoUtils.d.ts.map +1 -1
  69. package/build/utils/GeoUtils.js +3 -3
  70. package/build/utils/GeoUtils.js.map +1 -1
  71. package/build/utils/RouteUtils.d.ts.map +1 -1
  72. package/build/utils/RouteUtils.js +5 -2
  73. package/build/utils/RouteUtils.js.map +1 -1
  74. package/ios/ExpoGaodeMapView.swift +251 -13
  75. package/ios/ExpoGaodeMapViewModule.swift +5 -0
  76. package/ios/managers/UIManager.swift +3 -0
  77. package/ios/overlays/MarkerView.swift +205 -38
  78. package/package.json +9 -7
  79. 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 选择事件
@@ -113,9 +121,15 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
113
121
  private var pendingCameraMoveData: [String: Any]?
114
122
  private var cameraMoveDispatchWorkItem: DispatchWorkItem?
115
123
  private var lastCameraMoveDispatchTime: CFTimeInterval = 0
124
+ /// iOS 缩放惯性/连续 setZoom 可能多次触发 regionDidChange,合并后再派发 idle。
125
+ private var pendingCameraIdleData: [String: Any]?
126
+ private var cameraIdleDispatchWorkItem: DispatchWorkItem?
127
+ private let cameraIdleDebounceMs = 160
116
128
 
117
129
  /// 缩放手势识别器(用于模拟惯性)
118
130
  private var pinchGesture: UIPinchGestureRecognizer!
131
+ private var centerAnchoredPinchStartZoom: CGFloat?
132
+ private var centerAnchoredZoomCoordinate: CLLocationCoordinate2D?
119
133
 
120
134
  // 惯性动画相关属性
121
135
  private var displayLink: CADisplayLink?
@@ -361,12 +375,12 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
361
375
  uiManager.setMapType(1)
362
376
  uiManager.setShowsScale(showsScale)
363
377
  uiManager.setShowsCompass(showsCompass)
364
- uiManager.setZoomEnabled(isZoomEnabled)
378
+ applyZoomGestureSettings()
365
379
  uiManager.setScrollEnabled(isScrollEnabled)
366
380
  uiManager.setRotateEnabled(isRotateEnabled)
367
381
  uiManager.setTiltEnabled(isTiltEnabled)
368
382
  uiManager.setShowsUserLocation(showsUserLocation, followUser: followUserLocation)
369
- updatePinchGestureState()
383
+ applyUserLocationStyle()
370
384
  }
371
385
 
372
386
  /**
@@ -390,21 +404,21 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
390
404
 
391
405
  uiManager.setShowsScale(showsScale)
392
406
  uiManager.setShowsCompass(showsCompass)
393
- uiManager.setZoomEnabled(isZoomEnabled)
407
+ applyZoomGestureSettings()
394
408
  uiManager.setScrollEnabled(isScrollEnabled)
395
409
  uiManager.setRotateEnabled(isRotateEnabled)
396
410
  uiManager.setTiltEnabled(isTiltEnabled)
397
411
  uiManager.setShowsUserLocation(showsUserLocation, followUser: followUserLocation)
412
+ applyUserLocationStyle()
398
413
  uiManager.setShowsTraffic(showsTraffic)
399
414
  uiManager.setShowsBuildings(showsBuildings)
400
415
  uiManager.setShowsIndoorMap(showsIndoorMap)
401
- updatePinchGestureState()
402
416
  mapView.distanceFilter = distanceFilter
403
417
  mapView.headingFilter = headingFilter
404
418
  if let customMapStyleData {
405
419
  uiManager.setCustomMapStyle(customMapStyleData)
406
420
  }
407
-
421
+
408
422
  // 更新苹果地图样式
409
423
  updateAppleMapStyle()
410
424
 
@@ -439,6 +453,13 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
439
453
  // MARK: - 手势处理
440
454
 
441
455
  @objc func handlePinch(_ gesture: UIPinchGestureRecognizer) {
456
+ guard isZoomEnabled else { return }
457
+
458
+ if zoomGestureAnchor == "center" {
459
+ handleCenterAnchoredPinch(gesture)
460
+ return
461
+ }
462
+
442
463
  if gesture.state == .began {
443
464
  // 手势开始,立即停止之前的惯性动画,避免冲突
444
465
  stopInertiaAnimation()
@@ -455,6 +476,44 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
455
476
  }
456
477
  }
457
478
  }
479
+
480
+ private func handleCenterAnchoredPinch(_ gesture: UIPinchGestureRecognizer) {
481
+ switch gesture.state {
482
+ case .began:
483
+ stopInertiaAnimation()
484
+ centerAnchoredPinchStartZoom = mapView.zoomLevel
485
+ centerAnchoredZoomCoordinate = mapView.centerCoordinate
486
+ case .changed:
487
+ let startZoom = centerAnchoredPinchStartZoom ?? mapView.zoomLevel
488
+ let center = centerAnchoredZoomCoordinate ?? mapView.centerCoordinate
489
+ let zoomDelta = log2(Double(max(gesture.scale, 0.0001)))
490
+ applyCenterAnchoredZoom(CGFloat(Double(startZoom) + zoomDelta), center: center)
491
+ case .ended, .cancelled, .failed:
492
+ centerAnchoredPinchStartZoom = nil
493
+ let velocity = gesture.velocity
494
+ if abs(velocity) > 0.1 {
495
+ zoomVelocity = Double(velocity) * 0.02
496
+ if centerAnchoredZoomCoordinate == nil {
497
+ centerAnchoredZoomCoordinate = mapView.centerCoordinate
498
+ }
499
+ startInertiaAnimation()
500
+ } else {
501
+ centerAnchoredZoomCoordinate = nil
502
+ }
503
+ default:
504
+ break
505
+ }
506
+ }
507
+
508
+ private func applyCenterAnchoredZoom(_ zoom: CGFloat, center: CLLocationCoordinate2D) {
509
+ let validZoom = max(mapView.minZoomLevel, min(mapView.maxZoomLevel, zoom))
510
+ let status = MAMapStatus()
511
+ status.centerCoordinate = center
512
+ status.zoomLevel = validZoom
513
+ status.rotationDegree = mapView.rotationDegree
514
+ status.cameraDegree = mapView.cameraDegree
515
+ mapView.setMapStatus(status, animated: false, duration: 0)
516
+ }
458
517
 
459
518
  private func startInertiaAnimation() {
460
519
  stopInertiaAnimation()
@@ -476,12 +535,21 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
476
535
  // 碰到边界,停止动画
477
536
  newZoom = max(mapView.minZoomLevel, min(mapView.maxZoomLevel, newZoom))
478
537
  stopInertiaAnimation()
479
- mapView.setZoomLevel(newZoom, animated: false)
538
+ if zoomGestureAnchor == "center" {
539
+ applyCenterAnchoredZoom(newZoom, center: centerAnchoredZoomCoordinate ?? mapView.centerCoordinate)
540
+ centerAnchoredZoomCoordinate = nil
541
+ } else {
542
+ mapView.setZoomLevel(newZoom, animated: false)
543
+ }
480
544
  return
481
545
  }
482
546
 
483
547
  // 更新地图缩放级别(animated: false 以保证逐帧控制的流畅性)
484
- mapView.setZoomLevel(newZoom, animated: false)
548
+ if zoomGestureAnchor == "center" {
549
+ applyCenterAnchoredZoom(newZoom, center: centerAnchoredZoomCoordinate ?? mapView.centerCoordinate)
550
+ } else {
551
+ mapView.setZoomLevel(newZoom, animated: false)
552
+ }
485
553
 
486
554
  // 减速(应用摩擦力)
487
555
  zoomVelocity *= friction
@@ -489,6 +557,7 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
489
557
  // 停止条件
490
558
  if abs(zoomVelocity) < velocityThreshold {
491
559
  stopInertiaAnimation()
560
+ centerAnchoredZoomCoordinate = nil
492
561
  }
493
562
  }
494
563
 
@@ -568,6 +637,9 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
568
637
  func setFollowUserLocation(_ follow: Bool) {
569
638
  followUserLocation = follow
570
639
  uiManager?.setShowsUserLocation(showsUserLocation, followUser: follow)
640
+ if showsUserLocation {
641
+ applyUserLocationStyle()
642
+ }
571
643
  }
572
644
 
573
645
  func setShowsUserLocation(_ show: Bool) {
@@ -580,6 +652,7 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
580
652
 
581
653
  func setUserLocationRepresentation(_ config: [String: Any]) {
582
654
  userLocationRepresentation = config
655
+ hasAppliedUserLocationStyleAfterLocationUpdate = false
583
656
  if showsUserLocation {
584
657
  uiManager?.setUserLocationRepresentation(config)
585
658
  }
@@ -589,7 +662,7 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
589
662
  * 应用用户位置样式
590
663
  */
591
664
  private func applyUserLocationStyle() {
592
- guard let config = userLocationRepresentation else { return }
665
+ guard showsUserLocation, let config = userLocationRepresentation else { return }
593
666
  uiManager?.setUserLocationRepresentation(config)
594
667
  }
595
668
 
@@ -806,6 +879,9 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
806
879
  pendingCameraMoveData = nil
807
880
  cameraMoveDispatchWorkItem?.cancel()
808
881
  cameraMoveDispatchWorkItem = nil
882
+ pendingCameraIdleData = nil
883
+ cameraIdleDispatchWorkItem?.cancel()
884
+ cameraIdleDispatchWorkItem = nil
809
885
  if let privacyObserver {
810
886
  NotificationCenter.default.removeObserver(privacyObserver)
811
887
  }
@@ -861,6 +937,7 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
861
937
  super.addSubview(resolvedMapView)
862
938
  isMapLoaded = false
863
939
  hasAppliedInitialCameraPosition = false
940
+ hasAppliedUserLocationStyleAfterLocationUpdate = false
864
941
 
865
942
  cameraManager = CameraManager(mapView: resolvedMapView)
866
943
  uiManager = UIManager(mapView: resolvedMapView)
@@ -879,13 +956,20 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
879
956
  pinchGesture.delegate = self
880
957
  resolvedMapView.addGestureRecognizer(pinchGesture)
881
958
  self.pinchGesture = pinchGesture
882
- updatePinchGestureState()
959
+ applyZoomGestureSettings()
883
960
  }
884
961
 
885
- private func updatePinchGestureState() {
962
+ private func applyZoomGestureSettings() {
963
+ uiManager?.setZoomEnabled(isZoomEnabled && zoomGestureAnchor != "center")
886
964
  pinchGesture?.isEnabled = isZoomEnabled
965
+ if zoomGestureAnchor != "center" {
966
+ centerAnchoredPinchStartZoom = nil
967
+ centerAnchoredZoomCoordinate = nil
968
+ }
887
969
  if !isZoomEnabled {
888
970
  stopInertiaAnimation()
971
+ centerAnchoredPinchStartZoom = nil
972
+ centerAnchoredZoomCoordinate = nil
889
973
  }
890
974
  }
891
975
  }
@@ -937,9 +1021,37 @@ extension ExpoGaodeMapView {
937
1021
  if let styleData = customMapStyleData {
938
1022
  uiManager?.setCustomMapStyle(styleData)
939
1023
  }
1024
+
1025
+ applyUserLocationStyle()
940
1026
 
941
1027
  onLoad(["loaded": true])
942
1028
  }
1029
+
1030
+ /**
1031
+ * 定位更新回调
1032
+ */
1033
+ public func mapView(_ mapView: MAMapView, didUpdate userLocation: MAUserLocation, updatingLocation: Bool) {
1034
+ guard updatingLocation, let location = userLocation.location else { return }
1035
+
1036
+ if !hasAppliedUserLocationStyleAfterLocationUpdate {
1037
+ applyUserLocationStyle()
1038
+ hasAppliedUserLocationStyleAfterLocationUpdate = true
1039
+ }
1040
+
1041
+ let latitude = location.coordinate.latitude
1042
+ let longitude = location.coordinate.longitude
1043
+ guard latitude >= -90 && latitude <= 90,
1044
+ longitude >= -180 && longitude <= 180 else {
1045
+ return
1046
+ }
1047
+
1048
+ onLocation([
1049
+ "latitude": latitude,
1050
+ "longitude": longitude,
1051
+ "accuracy": location.horizontalAccuracy,
1052
+ "timestamp": Date().timeIntervalSince1970 * 1000
1053
+ ])
1054
+ }
943
1055
 
944
1056
  /**
945
1057
  * 地图区域即将改变时触发
@@ -958,7 +1070,7 @@ extension ExpoGaodeMapView {
958
1070
  */
959
1071
  public func mapView(_ mapView: MAMapView, regionDidChangeAnimated animated: Bool) {
960
1072
  flushPendingCameraMoveEvent()
961
- onCameraIdle(buildCameraEventData(for: mapView))
1073
+ scheduleCameraIdleEvent(buildCameraEventData(for: mapView))
962
1074
 
963
1075
  // 这里的 overlayViews 是 [UIView] 类型,可能包含 ClusterView
964
1076
  for view in overlayViews {
@@ -1038,6 +1150,27 @@ extension ExpoGaodeMapView {
1038
1150
  lastCameraMoveDispatchTime = timestamp
1039
1151
  onCameraMove(eventData)
1040
1152
  }
1153
+
1154
+ private func scheduleCameraIdleEvent(_ eventData: [String: Any]) {
1155
+ pendingCameraIdleData = eventData
1156
+ cameraIdleDispatchWorkItem?.cancel()
1157
+
1158
+ let workItem = DispatchWorkItem { [weak self] in
1159
+ guard let self = self, let latestEventData = self.pendingCameraIdleData else {
1160
+ return
1161
+ }
1162
+
1163
+ self.pendingCameraIdleData = nil
1164
+ self.cameraIdleDispatchWorkItem = nil
1165
+ self.onCameraIdle(latestEventData)
1166
+ }
1167
+
1168
+ cameraIdleDispatchWorkItem = workItem
1169
+ DispatchQueue.main.asyncAfter(
1170
+ deadline: .now() + Double(cameraIdleDebounceMs) / 1000.0,
1171
+ execute: workItem
1172
+ )
1173
+ }
1041
1174
 
1042
1175
  /**
1043
1176
  * 地图单击事件
@@ -1262,13 +1395,118 @@ extension ExpoGaodeMapView {
1262
1395
  handleGaodePoiTouch(pois)
1263
1396
  }
1264
1397
 
1398
+ private func getUserLocationAnnotationView(for mapView: MAMapView, annotation: MAAnnotation) -> MAAnnotationView? {
1399
+ guard let config = userLocationRepresentation,
1400
+ let imagePath = config["image"] as? String,
1401
+ !imagePath.isEmpty else {
1402
+ return nil
1403
+ }
1404
+
1405
+ let reuseId = "ExpoGaodeMapUserLocationStyleReuseIdentifier"
1406
+ let annotationView: MAAnnotationView
1407
+ if let reusableView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) {
1408
+ annotationView = reusableView
1409
+ } else {
1410
+ annotationView = MAAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
1411
+ }
1412
+ annotationView.annotation = annotation
1413
+ annotationView.canShowCallout = false
1414
+
1415
+ let imageWidth = config["imageWidth"] as? Double
1416
+ let imageHeight = config["imageHeight"] as? Double
1417
+ let cacheKey = userLocationImageCacheKey(imagePath: imagePath, imageWidth: imageWidth, imageHeight: imageHeight)
1418
+ annotationView.accessibilityIdentifier = cacheKey
1419
+
1420
+ if let cached = IconBitmapCache.shared.image(forKey: cacheKey) {
1421
+ annotationView.image = cached
1422
+ return annotationView
1423
+ }
1424
+
1425
+ annotationView.image = nil
1426
+ loadUserLocationImage(imagePath: imagePath, imageWidth: imageWidth, imageHeight: imageHeight, cacheKey: cacheKey) { [weak annotationView] image in
1427
+ guard let annotationView = annotationView,
1428
+ annotationView.accessibilityIdentifier == cacheKey else {
1429
+ return
1430
+ }
1431
+ annotationView.image = image
1432
+ }
1433
+
1434
+ return annotationView
1435
+ }
1436
+
1437
+ private func userLocationImageCacheKey(imagePath: String, imageWidth: Double?, imageHeight: Double?) -> String {
1438
+ let widthPart = imageWidth.map { String(Int($0.rounded())) } ?? "auto"
1439
+ let heightPart = imageHeight.map { String(Int($0.rounded())) } ?? "auto"
1440
+ return "userLocation|\(imagePath)|\(widthPart)x\(heightPart)"
1441
+ }
1442
+
1443
+ private func loadUserLocationImage(
1444
+ imagePath: String,
1445
+ imageWidth: Double?,
1446
+ imageHeight: Double?,
1447
+ cacheKey: String,
1448
+ completion: @escaping (UIImage?) -> Void
1449
+ ) {
1450
+ let finish: (UIImage?) -> Void = { image in
1451
+ if let image = image {
1452
+ IconBitmapCache.shared.setImage(image, forKey: cacheKey)
1453
+ }
1454
+ DispatchQueue.main.async {
1455
+ completion(image)
1456
+ }
1457
+ }
1458
+
1459
+ if imagePath.hasPrefix("http://") || imagePath.hasPrefix("https://") {
1460
+ DispatchQueue.global(qos: .userInitiated).async { [weak self] in
1461
+ guard let self = self,
1462
+ let url = URL(string: imagePath),
1463
+ let data = try? Data(contentsOf: url),
1464
+ let image = UIImage(data: data) else {
1465
+ finish(nil)
1466
+ return
1467
+ }
1468
+ finish(self.resizeUserLocationImage(image, imageWidth: imageWidth, imageHeight: imageHeight))
1469
+ }
1470
+ return
1471
+ }
1472
+
1473
+ let image: UIImage?
1474
+ if imagePath.hasPrefix("file://") {
1475
+ let path = String(imagePath.dropFirst(7))
1476
+ image = UIImage(contentsOfFile: path)
1477
+ } else {
1478
+ image = UIImage(named: imagePath)
1479
+ }
1480
+
1481
+ guard let image = image else {
1482
+ finish(nil)
1483
+ return
1484
+ }
1485
+ finish(resizeUserLocationImage(image, imageWidth: imageWidth, imageHeight: imageHeight))
1486
+ }
1487
+
1488
+ private func resizeUserLocationImage(_ image: UIImage, imageWidth: Double?, imageHeight: Double?) -> UIImage {
1489
+ guard let imageWidth = imageWidth,
1490
+ let imageHeight = imageHeight,
1491
+ imageWidth > 0,
1492
+ imageHeight > 0 else {
1493
+ return image
1494
+ }
1495
+
1496
+ let targetSize = CGSize(width: imageWidth, height: imageHeight)
1497
+ let renderer = UIGraphicsImageRenderer(size: targetSize)
1498
+ return renderer.image { _ in
1499
+ image.draw(in: CGRect(origin: .zero, size: targetSize))
1500
+ }
1501
+ }
1502
+
1265
1503
  /**
1266
1504
  * 创建标注视图
1267
- * 定位蓝点返回 nil 使用系统默认样式
1505
+ * 定位蓝点默认返回 nil 使用系统样式;传入 image 时按官方建议自定义定位 annotationView。
1268
1506
  */
1269
1507
  public func mapView(_ mapView: MAMapView, viewFor annotation: MAAnnotation) -> MAAnnotationView? {
1270
1508
  if annotation.isKind(of: MAUserLocation.self) {
1271
- return nil
1509
+ return getUserLocationAnnotationView(for: mapView, annotation: annotation)
1272
1510
  }
1273
1511
 
1274
1512
  // 🔑 支持 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
  // 精度圈填充颜色