expo-gaode-map-navigation 2.0.5 → 2.0.6

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 (55) hide show
  1. package/README.md +52 -1
  2. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapView.kt +182 -86
  3. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapViewModule.kt +5 -2
  4. package/android/src/main/java/expo/modules/gaodemap/map/managers/UIManager.kt +19 -5
  5. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerView.kt +319 -48
  6. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerViewModule.kt +3 -3
  7. package/build/index.d.ts +8 -4
  8. package/build/index.d.ts.map +1 -1
  9. package/build/index.js +79 -1
  10. package/build/index.js.map +1 -1
  11. package/build/map/ExpoGaodeMapModule.d.ts +4 -4
  12. package/build/map/ExpoGaodeMapModule.d.ts.map +1 -1
  13. package/build/map/ExpoGaodeMapModule.js +10 -8
  14. package/build/map/ExpoGaodeMapModule.js.map +1 -1
  15. package/build/map/ExpoGaodeMapView.d.ts.map +1 -1
  16. package/build/map/ExpoGaodeMapView.js +79 -17
  17. package/build/map/ExpoGaodeMapView.js.map +1 -1
  18. package/build/map/components/overlays/Cluster.d.ts.map +1 -1
  19. package/build/map/components/overlays/Cluster.js +12 -0
  20. package/build/map/components/overlays/Cluster.js.map +1 -1
  21. package/build/map/components/overlays/Marker.d.ts.map +1 -1
  22. package/build/map/components/overlays/Marker.js +70 -6
  23. package/build/map/components/overlays/Marker.js.map +1 -1
  24. package/build/map/types/common.types.d.ts +29 -5
  25. package/build/map/types/common.types.d.ts.map +1 -1
  26. package/build/map/types/common.types.js +5 -5
  27. package/build/map/types/common.types.js.map +1 -1
  28. package/build/map/types/index.d.ts +2 -1
  29. package/build/map/types/index.d.ts.map +1 -1
  30. package/build/map/types/index.js.map +1 -1
  31. package/build/map/types/location.types.d.ts +23 -0
  32. package/build/map/types/location.types.d.ts.map +1 -1
  33. package/build/map/types/location.types.js.map +1 -1
  34. package/build/map/types/map-view.types.d.ts +20 -22
  35. package/build/map/types/map-view.types.d.ts.map +1 -1
  36. package/build/map/types/map-view.types.js.map +1 -1
  37. package/build/map/types/overlays.types.d.ts +9 -2
  38. package/build/map/types/overlays.types.d.ts.map +1 -1
  39. package/build/map/types/overlays.types.js.map +1 -1
  40. package/build/map/types/route-playback.types.d.ts +12 -0
  41. package/build/map/types/route-playback.types.d.ts.map +1 -0
  42. package/build/map/types/route-playback.types.js +2 -0
  43. package/build/map/types/route-playback.types.js.map +1 -0
  44. package/build/types/route.types.d.ts +10 -1
  45. package/build/types/route.types.d.ts.map +1 -1
  46. package/build/types/route.types.js +2 -0
  47. package/build/types/route.types.js.map +1 -1
  48. package/ios/map/ExpoGaodeMapView.swift +151 -76
  49. package/ios/map/ExpoGaodeMapViewModule.swift +14 -1
  50. package/ios/map/managers/UIManager.swift +5 -4
  51. package/ios/map/overlays/ClusterView.swift +207 -147
  52. package/ios/map/overlays/ClusterViewModule.swift +5 -1
  53. package/ios/map/overlays/MarkerView.swift +214 -60
  54. package/ios/map/overlays/MarkerViewModule.swift +1 -1
  55. package/package.json +1 -1
@@ -73,6 +73,10 @@ class MarkerView: ExpoView {
73
73
  private var pendingAddTask: DispatchWorkItem?
74
74
  /// 延迟更新任务(批量处理 props 更新)
75
75
  private var pendingUpdateTask: DispatchWorkItem?
76
+ /// 子视图变化后的延迟刷新任务
77
+ private var pendingSubviewRefreshTask: DispatchWorkItem?
78
+ /// 最近一次应用到 annotationView 的 children 结构签名
79
+ private var lastRenderedChildrenSignature: String?
76
80
  /// 上次设置的地图引用(防止重复调用)
77
81
  private weak var lastSetMapView: MAMapView?
78
82
 
@@ -226,10 +230,13 @@ class MarkerView: ExpoView {
226
230
 
227
231
  // 1. 如果有 children,使用自定义视图
228
232
  if self.subviews.count > 0 {
229
- let key = cacheKey ?? "children_\(ObjectIdentifier(self).hashValue)"
233
+ let size = resolvedCustomSubviewSize(defaultSize: CGSize(width: 200, height: 60))
234
+ let key = childrenCacheKey(for: size)
230
235
  if let cached = IconBitmapCache.shared.image(forKey: key) {
231
236
  annotationView?.image = cached
232
- annotationView?.centerOffset = CGPoint(x: 0, y: 0)
237
+ if let annotationView = annotationView {
238
+ applyCenterOffset(to: annotationView, defaultOffset: .zero)
239
+ }
233
240
  return annotationView
234
241
  }
235
242
 
@@ -237,9 +244,10 @@ class MarkerView: ExpoView {
237
244
  DispatchQueue.main.async { [weak self, weak annotationView] in
238
245
  guard let self = self, let annotationView = annotationView else { return }
239
246
  if let generated = self.createImageFromSubviews() {
240
- IconBitmapCache.shared.setImage(generated, forKey: key)
241
247
  annotationView.image = generated
242
- annotationView.centerOffset = CGPoint(x: 0, y: 0)
248
+ self.applyCenterOffset(to: annotationView, defaultOffset: .zero)
249
+ } else if self.hasPendingImageContent() {
250
+ self.scheduleSubviewRefresh(allowFallbackToDefault: false)
243
251
  }
244
252
  }
245
253
  return annotationView
@@ -318,19 +326,19 @@ class MarkerView: ExpoView {
318
326
  self.annotationView = annotationView
319
327
 
320
328
  // 生成 cacheKey 或 fallback 到 identifier
321
- let key = cacheKey ?? "children_\(ObjectIdentifier(self).hashValue)"
329
+ let size = resolvedCustomSubviewSize(defaultSize: CGSize(width: 200, height: 40))
330
+ let key = childrenCacheKey(for: size)
322
331
 
323
332
  // 1) 如果缓存命中,直接同步返回图像(fast path)
324
333
  if let cached = IconBitmapCache.shared.image(forKey: key) {
325
334
  annotationView?.image = cached
326
- // 🔑 修复:自定义视图使用中心偏移,不需要底部偏移
327
- annotationView?.centerOffset = CGPoint(x: 0, y: 0)
335
+ if let annotationView = annotationView {
336
+ applyCenterOffset(to: annotationView, defaultOffset: .zero)
337
+ }
328
338
  return annotationView
329
339
  }
330
340
 
331
341
  // 2) 缓存未命中:返回占位(透明),并异步在主线程生成图像然后回填
332
- let size = CGSize(width: CGFloat(customViewWidth > 0 ? customViewWidth : 200),
333
- height: CGFloat(customViewHeight > 0 ? customViewHeight : 40))
334
342
  UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
335
343
  let transparentImage = UIGraphicsGetImageFromCurrentImageContext()
336
344
  UIGraphicsEndImageContext()
@@ -342,17 +350,16 @@ class MarkerView: ExpoView {
342
350
  // 再次检查缓存(避免重复渲染)
343
351
  if let cached = IconBitmapCache.shared.image(forKey: key) {
344
352
  annotationView.image = cached
345
- annotationView.centerOffset = CGPoint(x: 0, y: 0)
353
+ self.applyCenterOffset(to: annotationView, defaultOffset: .zero)
346
354
  return
347
355
  }
348
356
 
349
357
  // 调用你的原生渲染逻辑(保留空白检测、多次 layout)
350
358
  if let generated = self.createImageFromSubviews() {
351
- // 写入缓存(仅当用户传了 cacheKey 才缓存;否则建议仍缓存由 fingerprint 决定)
352
- IconBitmapCache.shared.setImage(generated, forKey: key)
353
359
  annotationView.image = generated
354
- annotationView.centerOffset = CGPoint(x: 0, y: 0)
355
- } else {
360
+ self.applyCenterOffset(to: annotationView, defaultOffset: .zero)
361
+ } else if self.hasPendingImageContent() {
362
+ self.scheduleSubviewRefresh(allowFallbackToDefault: false)
356
363
  }
357
364
  }
358
365
 
@@ -482,37 +489,21 @@ class MarkerView: ExpoView {
482
489
  * 将子视图转换为图片
483
490
  */
484
491
  private func createImageFromSubviews() -> UIImage? {
485
- // 🔑 如果有 cacheKey 且命中缓存,直接返回缓存图片
486
- if let key = cacheKey, let cachedImage = IconBitmapCache.shared.image(forKey: key) {
492
+ let size = resolvedCustomSubviewSize(defaultSize: CGSize(width: 200, height: 60))
493
+ let key = childrenCacheKey(for: size)
494
+
495
+ if let cachedImage = IconBitmapCache.shared.image(forKey: key) {
487
496
  return cachedImage
488
497
  }
489
498
 
490
499
  guard let firstSubview = subviews.first else {
491
500
  return nil
492
501
  }
493
-
494
- // 优先使用 customViewWidth/customViewHeight(用于 children),其次使用子视图尺寸,最后使用默认值
495
- let width: CGFloat
496
- let height: CGFloat
497
-
498
- if customViewWidth > 0 {
499
- width = CGFloat(customViewWidth)
500
- } else if firstSubview.bounds.size.width > 0 {
501
- width = firstSubview.bounds.size.width
502
- } else {
503
- width = 200 // 默认宽度
504
- }
505
-
506
- if customViewHeight > 0 {
507
- height = CGFloat(customViewHeight)
508
- } else if firstSubview.bounds.size.height > 0 {
509
- height = firstSubview.bounds.size.height
510
- } else {
511
- height = 60 // 默认高度
502
+
503
+ guard size.width > 0, size.height > 0 else {
504
+ return nil
512
505
  }
513
506
 
514
- let size = CGSize(width: width, height: height)
515
-
516
507
  // 强制子视图使用指定尺寸布局
517
508
  firstSubview.frame = CGRect(origin: .zero, size: size)
518
509
 
@@ -521,6 +512,10 @@ class MarkerView: ExpoView {
521
512
  forceLayoutRecursively(view: firstSubview)
522
513
  RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.01))
523
514
  }
515
+
516
+ if containsPendingImageContent(in: firstSubview) {
517
+ return nil
518
+ }
524
519
 
525
520
  UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
526
521
  defer { UIGraphicsEndImageContext() }
@@ -538,14 +533,120 @@ class MarkerView: ExpoView {
538
533
 
539
534
 
540
535
 
541
- // 🔑 写入缓存
542
- if let key = cacheKey {
543
- IconBitmapCache.shared.setImage(image, forKey: key)
544
- }
536
+ IconBitmapCache.shared.setImage(image, forKey: key)
545
537
 
546
538
  return image
547
539
  }
548
540
 
541
+ private func resolvedCustomSubviewSize(defaultSize: CGSize) -> CGSize {
542
+ guard let firstSubview = subviews.first else {
543
+ return defaultSize
544
+ }
545
+
546
+ if customViewWidth > 0 || customViewHeight > 0 {
547
+ let width = customViewWidth > 0 ? CGFloat(customViewWidth) : defaultSize.width
548
+ let height = customViewHeight > 0 ? CGFloat(customViewHeight) : defaultSize.height
549
+ return CGSize(width: width, height: height)
550
+ }
551
+
552
+ forceLayoutRecursively(view: firstSubview)
553
+
554
+ let compressedSize = firstSubview.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
555
+ let fittingSize = firstSubview.sizeThatFits(
556
+ CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
557
+ )
558
+ let intrinsicSize = firstSubview.intrinsicContentSize
559
+
560
+ let width = resolvedDimension(
561
+ candidates: [compressedSize.width, fittingSize.width, intrinsicSize.width, firstSubview.bounds.size.width],
562
+ fallback: defaultSize.width
563
+ )
564
+ let height = resolvedDimension(
565
+ candidates: [compressedSize.height, fittingSize.height, intrinsicSize.height, firstSubview.bounds.size.height],
566
+ fallback: defaultSize.height
567
+ )
568
+
569
+ return CGSize(width: width, height: height)
570
+ }
571
+
572
+ private func resolvedDimension(candidates: [CGFloat], fallback: CGFloat) -> CGFloat {
573
+ for value in candidates {
574
+ if value.isFinite && value > 0 {
575
+ return ceil(value)
576
+ }
577
+ }
578
+
579
+ return fallback
580
+ }
581
+
582
+ private func childrenCacheKey(for size: CGSize) -> String {
583
+ let signature = childrenRenderSignature()
584
+ let baseKey = cacheKey.map { "\($0)|\(signature)" }
585
+ ?? "children_\(ObjectIdentifier(self).hashValue)|\(signature)"
586
+ let roundedWidth = Int(ceil(size.width))
587
+ let roundedHeight = Int(ceil(size.height))
588
+ return "\(baseKey)|\(roundedWidth)x\(roundedHeight)"
589
+ }
590
+
591
+ private func childrenRenderSignature() -> String {
592
+ guard let firstSubview = subviews.first else {
593
+ return "empty"
594
+ }
595
+
596
+ var parts: [String] = []
597
+
598
+ func appendSignature(for view: UIView) {
599
+ parts.append(String(describing: type(of: view)))
600
+ let bounds = view.bounds
601
+ parts.append("b:\(Int(bounds.width.rounded()))x\(Int(bounds.height.rounded()))")
602
+
603
+ if let label = view as? UILabel {
604
+ parts.append("t:\(label.text ?? "")")
605
+ }
606
+
607
+ if let imageView = view as? UIImageView,
608
+ let image = imageView.image {
609
+ parts.append("i:\(Int(image.size.width.rounded()))x\(Int(image.size.height.rounded()))")
610
+ }
611
+
612
+ parts.append("c:\(view.subviews.count)")
613
+ for child in view.subviews {
614
+ appendSignature(for: child)
615
+ }
616
+ }
617
+
618
+ appendSignature(for: firstSubview)
619
+ return parts.joined(separator: "|")
620
+ }
621
+
622
+ private func hasPendingImageContent() -> Bool {
623
+ guard let firstSubview = subviews.first else {
624
+ return false
625
+ }
626
+
627
+ return containsPendingImageContent(in: firstSubview)
628
+ }
629
+
630
+ private func containsPendingImageContent(in view: UIView) -> Bool {
631
+ if view.isHidden || view.alpha <= 0 {
632
+ return false
633
+ }
634
+
635
+ if let imageView = view as? UIImageView {
636
+ let bounds = imageView.bounds
637
+ let hasSize = bounds.width > 0 || bounds.height > 0
638
+ if hasSize && imageView.image == nil {
639
+ return true
640
+ }
641
+ }
642
+
643
+ for subview in view.subviews where containsPendingImageContent(in: subview) {
644
+ return true
645
+ }
646
+
647
+ return false
648
+ }
649
+
549
650
 
550
651
  /**
551
652
  * 递归强制布局视图及其所有子视图
@@ -580,6 +681,7 @@ class MarkerView: ExpoView {
580
681
  isRemoving = true
581
682
  pendingAddTask?.cancel(); pendingAddTask = nil
582
683
  pendingUpdateTask?.cancel(); pendingUpdateTask = nil
684
+ pendingSubviewRefreshTask?.cancel(); pendingSubviewRefreshTask = nil
583
685
 
584
686
  guard let mapView = mapView else {
585
687
  isRemoving = false
@@ -621,19 +723,7 @@ class MarkerView: ExpoView {
621
723
  return
622
724
  }
623
725
 
624
- // 子视图移除后,需要刷新 annotation 视图
625
- if self.subviews.count <= 1 {
626
- // 所有子视图已移除,刷新以恢复默认图标
627
- if let mapView = mapView, let annotation = annotation {
628
- DispatchQueue.main.async { [weak self] in
629
- guard let self = self, !self.isRemoving else {
630
- return
631
- }
632
- mapView.removeAnnotation(annotation)
633
- mapView.addAnnotation(annotation)
634
- }
635
- }
636
- }
726
+ scheduleSubviewRefresh(allowFallbackToDefault: true)
637
727
  }
638
728
 
639
729
  override func didAddSubview(_ subview: UIView) {
@@ -644,14 +734,78 @@ class MarkerView: ExpoView {
644
734
  return
645
735
  }
646
736
 
647
- // 🔑 关键修复:刷新 annotation
648
- if let mapView = mapView, let annotation = annotation {
649
- // annotation 已存在,立即刷新
737
+ scheduleSubviewRefresh(allowFallbackToDefault: false)
738
+ }
739
+
740
+ private func scheduleSubviewRefresh(allowFallbackToDefault: Bool) {
741
+ pendingSubviewRefreshTask?.cancel()
742
+
743
+ let task = DispatchWorkItem { [weak self] in
744
+ guard let self = self, !self.isRemoving else { return }
745
+ self.refreshAnnotationForSubviewChanges(allowFallbackToDefault: allowFallbackToDefault)
746
+ }
747
+
748
+ pendingSubviewRefreshTask = task
749
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.02, execute: task)
750
+ }
751
+
752
+ private func refreshAnnotationForSubviewChanges(allowFallbackToDefault: Bool) {
753
+ guard let mapView = mapView else { return }
754
+
755
+ if annotation == nil {
756
+ updateAnnotation()
757
+ return
758
+ }
759
+
760
+ guard let annotation = annotation else { return }
761
+
762
+ if subviews.isEmpty {
763
+ if allowFallbackToDefault {
764
+ lastRenderedChildrenSignature = nil
765
+ annotationView = nil
766
+ mapView.removeAnnotation(annotation)
767
+ mapView.addAnnotation(annotation)
768
+ }
769
+ return
770
+ }
771
+
772
+ let signature = childrenRenderSignature()
773
+ if signature == lastRenderedChildrenSignature, annotationView?.image != nil {
774
+ return
775
+ }
776
+
777
+ invalidateCurrentChildrenCache()
778
+
779
+ if annotationView is MAPinAnnotationView {
780
+ annotationView = nil
650
781
  mapView.removeAnnotation(annotation)
651
782
  mapView.addAnnotation(annotation)
652
- } else if mapView != nil && annotation == nil {
653
- // annotation 还未创建,children 先添加了,触发创建
654
- updateAnnotation()
783
+ return
784
+ }
785
+
786
+ guard let annotationView = annotationView else {
787
+ mapView.removeAnnotation(annotation)
788
+ mapView.addAnnotation(annotation)
789
+ return
790
+ }
791
+
792
+ if let image = createImageFromSubviews() {
793
+ annotationView.image = image
794
+ applyCenterOffset(to: annotationView, defaultOffset: .zero)
795
+ annotationView.canShowCallout = false
796
+ annotationView.isDraggable = draggable
797
+ lastRenderedChildrenSignature = signature
798
+ }
799
+ }
800
+
801
+ private func invalidateCurrentChildrenCache() {
802
+ let sizes = [
803
+ resolvedCustomSubviewSize(defaultSize: CGSize(width: 200, height: 40)),
804
+ resolvedCustomSubviewSize(defaultSize: CGSize(width: 200, height: 60))
805
+ ]
806
+
807
+ for size in sizes {
808
+ IconBitmapCache.shared.removeImage(forKey: childrenCacheKey(for: size))
655
809
  }
656
810
  }
657
811
 
@@ -68,7 +68,7 @@ public class MarkerViewModule: Module {
68
68
  Prop("growAnimation") { (view: MarkerView, enabled: Bool) in
69
69
  view.growAnimation = enabled
70
70
  }
71
- Prop("cacheKey") { (view: MarkerView, key: String) in
71
+ Prop("cacheKey") { (view: MarkerView, key: String?) in
72
72
  view.setCacheKey(key)
73
73
  }
74
74
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-gaode-map-navigation",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
4
4
  "description": "高德地图导航功能模块 - 路径规划、导航引导,独立版本包含完整地图功能",
5
5
  "author": "TomWq <582752848@qq.com> (https://github.com/TomWq)",
6
6
  "repository": {