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
@@ -1,77 +1,74 @@
1
+ import Foundation
1
2
  import ExpoModulesCore
2
3
  import AMapNaviKit
3
4
 
4
5
  class ClusterView: ExpoView {
5
- // 属性
6
6
  var points: [[String: Any]] = [] {
7
7
  didSet {
8
8
  parsePoints()
9
9
  updateClusters()
10
10
  }
11
11
  }
12
- var radius: Int = 100 // 聚合范围 (screen points)
12
+ var radius: Int = 100
13
13
  var minClusterSize: Int = 1
14
14
  var clusterBuckets: [[String: Any]]?
15
-
16
- // 样式属性
15
+
17
16
  private var clusterBackgroundColor: UIColor = .systemBlue
18
17
  private var clusterBorderColor: UIColor = .white
19
18
  private var clusterBorderWidth: CGFloat = 2.0
20
19
  private var clusterTextColor: UIColor = .white
21
20
  private var clusterTextSize: CGFloat = 14.0
22
21
  private var clusterSize: CGSize = CGSize(width: 40, height: 40)
23
-
22
+
24
23
  let onClusterPress = EventDispatcher()
25
-
24
+
26
25
  private weak var mapView: MAMapView?
27
- // private var quadTree = CoordinateQuadTree() // Removed: using C++ ClusterNative
28
26
  private var currentAnnotations: [MAAnnotation] = []
29
27
  private let quadTreeQueue = DispatchQueue(label: "com.expo.gaode.quadtree")
30
28
  private var isInvalidated = false
31
-
32
- // 缓存坐标数据以加速 C++ 调用
29
+ private let imageCache = NSCache<NSString, UIImage>()
30
+ private var baseIconImage: UIImage?
31
+ private var iconIdentifier: String?
32
+
33
33
  private var latitudes: [Double] = []
34
34
  private var longitudes: [Double] = []
35
-
35
+
36
36
  required init(appContext: AppContext? = nil) {
37
37
  super.init(appContext: appContext)
38
38
  }
39
-
39
+
40
40
  private func parsePoints() {
41
41
  var lats: [Double] = []
42
42
  var lons: [Double] = []
43
-
43
+
44
44
  for point in points {
45
45
  if let coord = LatLngParser.parseLatLng(point) {
46
46
  lats.append(coord.latitude)
47
47
  lons.append(coord.longitude)
48
48
  } else {
49
- // 保持索引一致,无效点填 0
50
49
  lats.append(0)
51
50
  lons.append(0)
52
51
  }
53
52
  }
54
-
55
- self.latitudes = lats
56
- self.longitudes = lons
53
+
54
+ latitudes = lats
55
+ longitudes = lons
57
56
  }
58
-
59
- // MARK: - Setters for Expo Module
60
-
57
+
61
58
  func setPoints(_ points: [[String: Any]]) {
62
59
  self.points = points
63
60
  }
64
-
61
+
65
62
  func setRadius(_ radius: Int) {
66
63
  self.radius = radius
67
64
  updateClusters()
68
65
  }
69
-
66
+
70
67
  func setMinClusterSize(_ size: Int) {
71
68
  self.minClusterSize = size
72
69
  updateClusters()
73
70
  }
74
-
71
+
75
72
  func setMap(_ map: MAMapView) {
76
73
  self.mapView = map
77
74
  updateClusters()
@@ -79,116 +76,120 @@ class ClusterView: ExpoView {
79
76
 
80
77
  func setClusterStyle(_ style: [String: Any]) {
81
78
  if let color = ColorParser.parseColor(style["backgroundColor"]) {
82
- self.clusterBackgroundColor = color
79
+ clusterBackgroundColor = color
83
80
  }
84
81
  if let borderColor = ColorParser.parseColor(style["borderColor"]) {
85
- self.clusterBorderColor = borderColor
82
+ clusterBorderColor = borderColor
86
83
  }
87
84
  if let borderWidth = style["borderWidth"] as? Double {
88
- self.clusterBorderWidth = CGFloat(borderWidth)
85
+ clusterBorderWidth = CGFloat(borderWidth)
89
86
  }
90
-
91
- // 尺寸设置
87
+
92
88
  if let width = style["width"] as? Double {
93
- self.clusterSize.width = CGFloat(width)
94
- // 如果只设置了宽度,默认高度等于宽度(正圆)
89
+ clusterSize.width = CGFloat(width)
95
90
  if style["height"] == nil {
96
- self.clusterSize.height = CGFloat(width)
91
+ clusterSize.height = CGFloat(width)
97
92
  }
98
93
  }
99
-
94
+
100
95
  if let height = style["height"] as? Double {
101
- self.clusterSize.height = CGFloat(height)
102
- // 如果只设置了高度,默认宽度等于高度
96
+ clusterSize.height = CGFloat(height)
103
97
  if style["width"] == nil {
104
- self.clusterSize.width = CGFloat(height)
98
+ clusterSize.width = CGFloat(height)
105
99
  }
106
100
  }
107
-
101
+
102
+ clearImageCache()
108
103
  updateClusters()
109
104
  }
110
-
105
+
106
+ func setIcon(_ icon: String?) {
107
+ iconIdentifier = icon
108
+ baseIconImage = nil
109
+ clearImageCache()
110
+
111
+ guard let icon, !icon.isEmpty else {
112
+ updateClusters()
113
+ return
114
+ }
115
+
116
+ loadIcon(from: icon)
117
+ }
118
+
111
119
  func setClusterTextStyle(_ style: [String: Any]) {
112
120
  if let color = ColorParser.parseColor(style["color"]) {
113
- self.clusterTextColor = color
121
+ clusterTextColor = color
114
122
  }
115
123
  if let fontSize = style["fontSize"] as? Double {
116
- self.clusterTextSize = CGFloat(fontSize)
124
+ clusterTextSize = CGFloat(fontSize)
117
125
  }
126
+ clearImageCache()
118
127
  updateClusters()
119
128
  }
120
129
 
121
130
  func setClusterBuckets(_ buckets: [[String: Any]]) {
122
- self.clusterBuckets = buckets
131
+ clusterBuckets = buckets
132
+ clearImageCache()
123
133
  updateClusters()
124
134
  }
125
135
 
126
136
  func mapRegionDidChange() {
127
137
  updateClusters()
128
138
  }
129
-
130
- // MARK: - Update Logic
131
-
139
+
132
140
  private var updateTimer: Timer?
133
- private let throttleInterval: TimeInterval = 0.3 // 300ms 节流
141
+ private let throttleInterval: TimeInterval = 0.3
134
142
 
135
143
  func updateClusters() {
136
144
  if isInvalidated { return }
137
-
138
- // 节流逻辑:取消上一次的 Timer,重新计时
145
+
139
146
  updateTimer?.invalidate()
140
147
  updateTimer = Timer.scheduledTimer(withTimeInterval: throttleInterval, repeats: false) { [weak self] _ in
141
148
  self?.performUpdate()
142
149
  }
143
150
  }
144
-
151
+
145
152
  private func performUpdate() {
146
153
  if isInvalidated { return }
147
154
  guard let mapView = mapView else { return }
148
-
149
- // 确保地图已布局
150
155
  if mapView.bounds.size.width == 0 { return }
151
-
156
+
152
157
  let visibleRect = mapView.visibleMapRect
153
158
  let zoomScale = visibleRect.size.width / Double(mapView.bounds.size.width)
154
-
155
- // 计算当前缩放级别下的物理半径(米)
156
- // MAMapView 单位投影:1 map point ≈ 1 meter (at equator)
157
- // 实际上需要根据纬度计算 metersPerMapPoint
158
159
  let centerLat = mapView.centerCoordinate.latitude
159
160
  let metersPerMapPoint = MAMetersPerMapPointAtLatitude(centerLat)
160
161
  let mapPointsPerScreenPoint = zoomScale
161
162
  let metersPerScreenPoint = metersPerMapPoint * mapPointsPerScreenPoint
162
- let radiusMeters = Double(self.radius) * metersPerScreenPoint
163
-
164
- // 在后台串行队列计算聚合
163
+ let radiusMeters = Double(radius) * metersPerScreenPoint
164
+
165
165
  quadTreeQueue.async { [weak self] in
166
- guard let self = self else { return }
167
-
168
- // 转换为 NSNumber 数组以传递给 Obj-C++
166
+ guard let self else { return }
167
+
169
168
  let latNums = self.latitudes.map { NSNumber(value: $0) }
170
169
  let lonNums = self.longitudes.map { NSNumber(value: $0) }
171
-
172
- // 调用 C++ 聚类算法
173
- let clusterData = ClusterNative.clusterPoints(latitudes: latNums, longitudes: lonNums, radiusMeters: radiusMeters)
174
-
170
+ let clusterData = ClusterNative.clusterPoints(
171
+ latitudes: latNums,
172
+ longitudes: lonNums,
173
+ radiusMeters: radiusMeters
174
+ )
175
+
175
176
  var annotations: [ClusterAnnotation] = []
176
-
177
+
177
178
  if clusterData.count > 0 {
178
179
  let clusterCount = clusterData[0].intValue
179
180
  var offset = 1
180
-
181
+
181
182
  for _ in 0..<clusterCount {
182
183
  if offset >= clusterData.count { break }
183
-
184
+
184
185
  let centerIndex = clusterData[offset].intValue
185
186
  offset += 1
186
-
187
+
187
188
  let count = clusterData[offset].intValue
188
189
  offset += 1
189
-
190
+
190
191
  var pois: [[String: Any]] = []
191
-
192
+
192
193
  for _ in 0..<count {
193
194
  if offset < clusterData.count {
194
195
  let idx = clusterData[offset].intValue
@@ -198,111 +199,97 @@ class ClusterView: ExpoView {
198
199
  offset += 1
199
200
  }
200
201
  }
201
-
202
+
202
203
  if centerIndex >= 0 && centerIndex < self.points.count {
203
204
  let centerPoint = self.points[centerIndex]
204
205
  if let lat = centerPoint["latitude"] as? Double,
205
206
  let lon = centerPoint["longitude"] as? Double {
206
- let coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lon)
207
- // 只有当聚类点数量 >= minClusterSize 时才显示聚类
208
- // 但通常 ClusterView 负责显示所有点(聚合或非聚合)
209
- // 这里如果 count == 1,也是一个 ClusterAnnotation,只是显示样式可能不同
210
- let annotation = ClusterAnnotation(coordinate: coordinate, count: count, pois: pois)
211
- annotations.append(annotation)
207
+ let coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lon)
208
+ annotations.append(
209
+ ClusterAnnotation(coordinate: coordinate, count: count, pois: pois)
210
+ )
212
211
  }
213
212
  }
214
213
  }
215
214
  }
216
-
215
+
217
216
  DispatchQueue.main.async {
218
217
  if self.isInvalidated { return }
219
218
  self.updateMapViewAnnotations(with: annotations as [MAAnnotation])
220
219
  }
221
220
  }
222
221
  }
223
-
222
+
224
223
  private func updateMapViewAnnotations(with newAnnotations: [MAAnnotation]) {
225
224
  guard let mapView = mapView else { return }
226
-
227
- // Diff 算法:找出新增、移除和保留的标注
228
- // 注意:ClusterAnnotation 需要实现 isEqual 和 hash
229
-
225
+
230
226
  let before = Set(currentAnnotations.compactMap { $0 as? ClusterAnnotation })
231
227
  let after = Set(newAnnotations.compactMap { $0 as? ClusterAnnotation })
232
-
233
- // intersection 返回 before 中的元素(如果相等),这正是我们需要保留的已经在地图上的实例
228
+
234
229
  let toKeep = before.intersection(after)
235
230
  let toAdd = after.subtracting(toKeep)
236
231
  let toRemove = before.subtracting(toKeep)
237
-
238
- // 只有当有变化时才操作
232
+
239
233
  if !toRemove.isEmpty {
240
234
  mapView.removeAnnotations(Array(toRemove))
241
235
  }
242
-
236
+
243
237
  if !toAdd.isEmpty {
244
238
  mapView.addAnnotations(Array(toAdd))
245
239
  }
246
-
247
- // 更新 currentAnnotations
248
- // 关键:必须保留已经在地图上的实例 (toKeep),加上新增的实例 (toAdd)
249
- // 这样可以保证 currentAnnotations 中的对象始终与地图上的对象一致,避免 KVO 崩溃
240
+
250
241
  var nextAnnotations: [MAAnnotation] = []
251
242
  nextAnnotations.append(contentsOf: Array(toKeep) as [MAAnnotation])
252
243
  nextAnnotations.append(contentsOf: Array(toAdd) as [MAAnnotation])
253
-
254
244
  currentAnnotations = nextAnnotations
255
245
  }
256
-
257
- // MARK: - View Provider
258
-
246
+
259
247
  func viewForAnnotation(_ annotation: MAAnnotation) -> MAAnnotationView? {
260
248
  guard let clusterAnnotation = annotation as? ClusterAnnotation else { return nil }
261
-
249
+
262
250
  let reuseIdentifier = "ClusterAnnotation"
263
251
  var annotationView = mapView?.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)
264
-
252
+
265
253
  if annotationView == nil {
266
254
  annotationView = MAAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
267
255
  }
268
-
256
+
269
257
  annotationView?.annotation = annotation
270
258
  annotationView?.canShowCallout = false
271
-
272
- // 生成图标
273
259
  annotationView?.image = image(for: clusterAnnotation.count)
274
260
  annotationView?.centerOffset = CGPoint(x: 0, y: 0)
275
261
  annotationView?.zIndex = 100
276
-
262
+
277
263
  return annotationView
278
264
  }
279
-
265
+
280
266
  private func image(for count: Int) -> UIImage? {
281
- let size = self.clusterSize
267
+ let cacheKey = clusterImageCacheKey(for: count)
268
+ if let cachedImage = imageCache.object(forKey: cacheKey as NSString) {
269
+ return cachedImage
270
+ }
271
+
272
+ let size = clusterSize
282
273
  let renderer = UIGraphicsImageRenderer(size: size)
283
-
284
- return renderer.image { context in
274
+
275
+ let renderedImage = renderer.image { _ in
285
276
  let rect = CGRect(origin: .zero, size: size)
286
-
287
- // 基础样式
288
- var bgColor = self.clusterBackgroundColor
289
- var borderColor = self.clusterBorderColor
290
- var borderWidth = self.clusterBorderWidth
291
-
292
- // 应用分级样式
293
- if let buckets = self.clusterBuckets {
277
+
278
+ var bgColor = clusterBackgroundColor
279
+ var borderColor = clusterBorderColor
280
+ var borderWidth = clusterBorderWidth
281
+
282
+ if let buckets = clusterBuckets {
294
283
  var bestBucket: [String: Any]?
295
284
  var maxMinPoints = -1
296
-
285
+
297
286
  for bucket in buckets {
298
- if let minPoints = bucket["minPoints"] as? Int, minPoints <= count {
299
- if minPoints > maxMinPoints {
300
- maxMinPoints = minPoints
301
- bestBucket = bucket
302
- }
287
+ if let minPoints = bucket["minPoints"] as? Int, minPoints <= count, minPoints > maxMinPoints {
288
+ maxMinPoints = minPoints
289
+ bestBucket = bucket
303
290
  }
304
291
  }
305
-
292
+
306
293
  if let bucket = bestBucket {
307
294
  if let c = ColorParser.parseColor(bucket["backgroundColor"]) {
308
295
  bgColor = c
@@ -315,43 +302,117 @@ class ClusterView: ExpoView {
315
302
  }
316
303
  }
317
304
  }
318
-
319
- bgColor.setFill()
320
- UIBezierPath(ovalIn: rect).fill()
321
-
322
- // 绘制边框
323
- if borderWidth > 0 {
324
- borderColor.setStroke()
325
- let path = UIBezierPath(ovalIn: rect.insetBy(dx: borderWidth / 2, dy: borderWidth / 2))
326
- path.lineWidth = borderWidth
327
- path.stroke()
305
+
306
+ if let baseIconImage {
307
+ baseIconImage.draw(in: rect)
308
+ if borderWidth > 0 {
309
+ borderColor.setStroke()
310
+ let path = UIBezierPath(ovalIn: rect.insetBy(dx: borderWidth / 2, dy: borderWidth / 2))
311
+ path.lineWidth = borderWidth
312
+ path.stroke()
313
+ }
314
+ } else {
315
+ bgColor.setFill()
316
+ UIBezierPath(ovalIn: rect).fill()
317
+
318
+ if borderWidth > 0 {
319
+ borderColor.setStroke()
320
+ let path = UIBezierPath(ovalIn: rect.insetBy(dx: borderWidth / 2, dy: borderWidth / 2))
321
+ path.lineWidth = borderWidth
322
+ path.stroke()
323
+ }
328
324
  }
329
-
330
- // 绘制文字
325
+
331
326
  let text = "\(count)"
332
327
  let attributes: [NSAttributedString.Key: Any] = [
333
- .font: UIFont.boldSystemFont(ofSize: self.clusterTextSize),
334
- .foregroundColor: self.clusterTextColor
328
+ .font: UIFont.boldSystemFont(ofSize: clusterTextSize),
329
+ .foregroundColor: clusterTextColor
335
330
  ]
336
331
  let textSize = text.size(withAttributes: attributes)
337
- let textRect = CGRect(x: (size.width - textSize.width) / 2,
338
- y: (size.height - textSize.height) / 2,
339
- width: textSize.width,
340
- height: textSize.height)
332
+ let textRect = CGRect(
333
+ x: (size.width - textSize.width) / 2,
334
+ y: (size.height - textSize.height) / 2,
335
+ width: textSize.width,
336
+ height: textSize.height
337
+ )
341
338
  text.draw(in: textRect, withAttributes: attributes)
342
339
  }
340
+
341
+ imageCache.setObject(renderedImage, forKey: cacheKey as NSString)
342
+ return renderedImage
343
+ }
344
+
345
+ private func clusterImageCacheKey(for count: Int) -> String {
346
+ let bucketKey: String
347
+ if let buckets = clusterBuckets {
348
+ let minPoints = buckets
349
+ .compactMap { $0["minPoints"] as? Int }
350
+ .filter { $0 <= count }
351
+ .max() ?? 0
352
+ bucketKey = "bucket:\(minPoints)"
353
+ } else {
354
+ bucketKey = "bucket:none"
355
+ }
356
+
357
+ return [
358
+ "count:\(count)",
359
+ bucketKey,
360
+ "size:\(Int(clusterSize.width.rounded()))x\(Int(clusterSize.height.rounded()))",
361
+ "bg:\(clusterBackgroundColor.description)",
362
+ "border:\(clusterBorderColor.description)",
363
+ "borderWidth:\(clusterBorderWidth)",
364
+ "textColor:\(clusterTextColor.description)",
365
+ "textSize:\(clusterTextSize)",
366
+ "icon:\(iconIdentifier ?? "none")"
367
+ ].joined(separator: "|")
368
+ }
369
+
370
+ private func clearImageCache() {
371
+ imageCache.removeAllObjects()
372
+ }
373
+
374
+ private func loadIcon(from icon: String) {
375
+ let requestedIcon = icon
376
+
377
+ DispatchQueue.global().async { [weak self] in
378
+ guard let self else { return }
379
+
380
+ let image: UIImage? = {
381
+ if requestedIcon.hasPrefix("http://") || requestedIcon.hasPrefix("https://") {
382
+ guard let url = URL(string: requestedIcon),
383
+ let data = try? Data(contentsOf: url) else {
384
+ return nil
385
+ }
386
+ return UIImage(data: data)
387
+ }
388
+
389
+ if requestedIcon.hasPrefix("file://") {
390
+ let path = String(requestedIcon.dropFirst(7))
391
+ return UIImage(contentsOfFile: path)
392
+ }
393
+
394
+ return UIImage(named: requestedIcon) ?? UIImage(contentsOfFile: requestedIcon)
395
+ }()
396
+
397
+ guard let image else { return }
398
+
399
+ DispatchQueue.main.async { [weak self] in
400
+ guard let self, self.iconIdentifier == requestedIcon else { return }
401
+ self.baseIconImage = image
402
+ self.clearImageCache()
403
+ self.updateClusters()
404
+ }
405
+ }
343
406
  }
344
-
345
- // MARK: - Event Handling
346
-
407
+
347
408
  func containsAnnotation(_ annotation: MAAnnotation) -> Bool {
348
409
  guard let clusterAnnotation = annotation as? ClusterAnnotation else { return false }
349
410
  return currentAnnotations.contains { $0.isEqual(clusterAnnotation) }
350
411
  }
351
-
412
+
352
413
  func handleAnnotationTap(_ annotation: MAAnnotation) {
353
414
  guard let clusterAnnotation = annotation as? ClusterAnnotation else { return }
354
-
415
+
355
416
  onClusterPress([
356
417
  "count": clusterAnnotation.count,
357
418
  "latitude": clusterAnnotation.coordinate.latitude,
@@ -359,12 +420,11 @@ class ClusterView: ExpoView {
359
420
  "pois": clusterAnnotation.pois
360
421
  ])
361
422
  }
362
-
363
- // MARK: - Lifecycle
364
-
423
+
365
424
  override func removeFromSuperview() {
366
425
  isInvalidated = true
367
- updateTimer?.invalidate() // 清理定时器
426
+ updateTimer?.invalidate()
427
+ clearImageCache()
368
428
  super.removeFromSuperview()
369
429
  mapView?.removeAnnotations(currentAnnotations)
370
430
  }
@@ -22,6 +22,10 @@ public class ClusterViewModule: Module {
22
22
  Prop("clusterStyle") { (view: ClusterView, style: [String: Any]) in
23
23
  view.setClusterStyle(style)
24
24
  }
25
+
26
+ Prop("icon") { (view: ClusterView, icon: String?) in
27
+ view.setIcon(icon)
28
+ }
25
29
 
26
30
  Prop("clusterTextStyle") { (view: ClusterView, style: [String: Any]) in
27
31
  view.setClusterTextStyle(style)
@@ -32,4 +36,4 @@ public class ClusterViewModule: Module {
32
36
  }
33
37
  }
34
38
  }
35
- }
39
+ }