react-native-google-maps-plus 1.7.0-dev.9 → 1.8.0-dev.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.
Files changed (88) hide show
  1. package/android/build.gradle +1 -1
  2. package/android/gradle.properties +2 -1
  3. package/android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt +124 -128
  4. package/android/src/main/java/com/rngooglemapsplus/MapCircleBuilder.kt +2 -3
  5. package/android/src/main/java/com/rngooglemapsplus/MapMarkerBuilder.kt +83 -53
  6. package/android/src/main/java/com/rngooglemapsplus/MapPolygonBuilder.kt +6 -23
  7. package/android/src/main/java/com/rngooglemapsplus/MapPolylineBuilder.kt.kt +12 -39
  8. package/android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt +34 -16
  9. package/android/src/main/java/com/rngooglemapsplus/extensions/BitmapExtension.kt +35 -0
  10. package/android/src/main/java/com/rngooglemapsplus/extensions/CameraPositionExtension.kt +3 -2
  11. package/android/src/main/java/com/rngooglemapsplus/extensions/LatLngBoundsExtension.kt +31 -0
  12. package/android/src/main/java/com/rngooglemapsplus/extensions/MapObjectTagExtensions.kt +84 -0
  13. package/android/src/main/java/com/rngooglemapsplus/extensions/RNLineCapTypeExtension.kt +14 -0
  14. package/android/src/main/java/com/rngooglemapsplus/extensions/RNLineJoinTypeExtension.kt +12 -0
  15. package/android/src/main/java/com/rngooglemapsplus/extensions/RNMapCircleExtension.kt +7 -1
  16. package/android/src/main/java/com/rngooglemapsplus/extensions/RNMarkerExtension.kt +54 -17
  17. package/android/src/main/java/com/rngooglemapsplus/extensions/RNPolygonExtension.kt +31 -1
  18. package/android/src/main/java/com/rngooglemapsplus/extensions/RNPolylineExtension.kt +6 -1
  19. package/ios/GoogleMapViewImpl.swift +89 -69
  20. package/ios/LocationHandler.swift +3 -1
  21. package/ios/MapCircleBuilder.swift +2 -3
  22. package/ios/MapHelper.swift +3 -5
  23. package/ios/MapMarkerBuilder.swift +147 -91
  24. package/ios/MapPolygonBuilder.swift +6 -41
  25. package/ios/MapPolylineBuilder.swift +2 -10
  26. package/ios/RNGoogleMapsPlusView.swift +49 -33
  27. package/ios/extensions/GMSCameraPosition+Extension.swift +2 -2
  28. package/ios/extensions/MapObjectTag+Extension.swift +93 -0
  29. package/ios/extensions/RNCircle+Extension.swift +14 -5
  30. package/ios/extensions/RNLatLng+Extension.swift +11 -0
  31. package/ios/extensions/RNLineCapType+Extension.swift +10 -0
  32. package/ios/extensions/RNLineJoinType+Extension.swift +11 -0
  33. package/ios/extensions/RNMarker+Extension.swift +43 -12
  34. package/ios/extensions/RNPolygon+Extension.swift.swift +50 -21
  35. package/ios/extensions/RNPolyline+Extension.swift.swift +15 -26
  36. package/ios/extensions/SVGKImage+Extension.swift +22 -0
  37. package/ios/extensions/UIImage+Extension.swift +45 -0
  38. package/lib/module/types.js.map +1 -1
  39. package/lib/typescript/src/RNGoogleMapsPlusView.nitro.d.ts +17 -15
  40. package/lib/typescript/src/RNGoogleMapsPlusView.nitro.d.ts.map +1 -1
  41. package/lib/typescript/src/types.d.ts +8 -1
  42. package/lib/typescript/src/types.d.ts.map +1 -1
  43. package/nitrogen/generated/android/RNGoogleMapsPlusOnLoad.cpp +8 -8
  44. package/nitrogen/generated/android/c++/{JFunc_void_RNRegion_RNCamera.hpp → JFunc_void_RNRegion_RNCameraChange.hpp} +21 -22
  45. package/nitrogen/generated/android/c++/JFunc_void_RNRegion_RNCameraChange_bool.hpp +82 -0
  46. package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +75 -0
  47. package/nitrogen/generated/android/c++/JFunc_void_std__string_RNLatLng.hpp +77 -0
  48. package/nitrogen/generated/android/c++/JHybridRNGoogleMapsPlusViewSpec.cpp +142 -130
  49. package/nitrogen/generated/android/c++/JHybridRNGoogleMapsPlusViewSpec.hpp +30 -28
  50. package/nitrogen/generated/android/c++/JRNCameraChange.hpp +70 -0
  51. package/nitrogen/generated/android/c++/JRNInitialProps.hpp +7 -3
  52. package/nitrogen/generated/android/c++/JRNMarker.hpp +7 -7
  53. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rngooglemapsplus/{Func_void_RNRegion_RNCamera.kt → Func_void_RNRegion_RNCameraChange.kt} +9 -9
  54. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rngooglemapsplus/{Func_void_RNRegion_RNCamera_bool.kt → Func_void_RNRegion_RNCameraChange_bool.kt} +9 -9
  55. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rngooglemapsplus/{Func_void_std__optional_std__string_.kt → Func_void_std__string.kt} +12 -12
  56. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rngooglemapsplus/{Func_void_std__optional_std__string__RNLatLng.kt → Func_void_std__string_RNLatLng.kt} +12 -12
  57. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rngooglemapsplus/HybridRNGoogleMapsPlusViewSpec.kt +50 -42
  58. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rngooglemapsplus/RNCameraChange.kt +46 -0
  59. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rngooglemapsplus/RNInitialProps.kt +6 -3
  60. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rngooglemapsplus/RNMarker.kt +6 -6
  61. package/nitrogen/generated/ios/RNGoogleMapsPlus-Swift-Cxx-Bridge.cpp +24 -16
  62. package/nitrogen/generated/ios/RNGoogleMapsPlus-Swift-Cxx-Bridge.hpp +97 -72
  63. package/nitrogen/generated/ios/RNGoogleMapsPlus-Swift-Cxx-Umbrella.hpp +3 -0
  64. package/nitrogen/generated/ios/c++/HybridRNGoogleMapsPlusViewSpecSwift.hpp +43 -28
  65. package/nitrogen/generated/ios/swift/{Func_void_RNRegion_RNCamera.swift → Func_void_RNRegion_RNCameraChange.swift} +10 -10
  66. package/nitrogen/generated/ios/swift/{Func_void_RNRegion_RNCamera_bool.swift → Func_void_RNRegion_RNCameraChange_bool.swift} +10 -10
  67. package/nitrogen/generated/ios/swift/Func_void_std__optional_std__string_.swift +6 -6
  68. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +47 -0
  69. package/nitrogen/generated/ios/swift/Func_void_std__string_RNLatLng.swift +47 -0
  70. package/nitrogen/generated/ios/swift/HybridRNGoogleMapsPlusViewSpec.swift +16 -14
  71. package/nitrogen/generated/ios/swift/HybridRNGoogleMapsPlusViewSpec_cxx.swift +186 -224
  72. package/nitrogen/generated/ios/swift/RNCameraChange.swift +68 -0
  73. package/nitrogen/generated/ios/swift/RNInitialProps.swift +31 -1
  74. package/nitrogen/generated/ios/swift/RNMarker.swift +24 -31
  75. package/nitrogen/generated/shared/c++/HybridRNGoogleMapsPlusViewSpec.cpp +2 -0
  76. package/nitrogen/generated/shared/c++/HybridRNGoogleMapsPlusViewSpec.hpp +36 -31
  77. package/nitrogen/generated/shared/c++/RNCameraChange.hpp +88 -0
  78. package/nitrogen/generated/shared/c++/RNInitialProps.hpp +6 -2
  79. package/nitrogen/generated/shared/c++/RNMarker.hpp +6 -6
  80. package/nitrogen/generated/shared/c++/views/HybridRNGoogleMapsPlusViewComponent.cpp +28 -28
  81. package/nitrogen/generated/shared/c++/views/HybridRNGoogleMapsPlusViewComponent.hpp +15 -15
  82. package/package.json +1 -1
  83. package/src/RNGoogleMapsPlusView.nitro.ts +19 -14
  84. package/src/types.ts +9 -1
  85. package/nitrogen/generated/android/c++/JFunc_void_RNRegion_RNCamera_bool.hpp +0 -83
  86. package/nitrogen/generated/android/c++/JFunc_void_std__optional_std__string_.hpp +0 -76
  87. package/nitrogen/generated/android/c++/JFunc_void_std__optional_std__string__RNLatLng.hpp +0 -78
  88. package/nitrogen/generated/ios/swift/Func_void_std__optional_std__string__RNLatLng.swift +0 -54
@@ -15,12 +15,13 @@ final class MapMarkerBuilder {
15
15
  let marker = GMSMarker(
16
16
  position: m.coordinate.toCLLocationCoordinate2D()
17
17
  )
18
- marker.userData = m.id
19
- marker.tracksViewChanges = true
20
18
  marker.icon = icon
21
19
  m.title.map { marker.title = $0 }
22
20
  m.snippet.map { marker.snippet = $0 }
23
- m.opacity.map { marker.iconView?.alpha = CGFloat($0) }
21
+ m.opacity.map {
22
+ marker.opacity = Float($0)
23
+ marker.iconView?.alpha = CGFloat($0)
24
+ }
24
25
  m.flat.map { marker.isFlat = $0 }
25
26
  m.draggable.map { marker.isDraggable = $0 }
26
27
  m.rotation.map { marker.rotation = $0 }
@@ -35,101 +36,127 @@ final class MapMarkerBuilder {
35
36
  }
36
37
  m.zIndex.map { marker.zIndex = Int32($0) }
37
38
 
38
- onMainAsync { [weak marker] in
39
- try? await Task.sleep(nanoseconds: 250_000_000)
40
- marker?.tracksViewChanges = false
41
- }
39
+ marker.tagData = MarkerTag(
40
+ id: m.id,
41
+ iconSvg: m.infoWindowIconSvg
42
+ )
42
43
 
43
44
  return marker
44
45
  }
45
46
 
46
47
  @MainActor
47
48
  func update(_ prev: RNMarker, _ next: RNMarker, _ m: GMSMarker) {
48
- if prev.coordinate.latitude != next.coordinate.latitude
49
- || prev.coordinate.longitude != next.coordinate.longitude {
50
- m.position = next.coordinate.toCLLocationCoordinate2D()
51
- }
52
-
53
- if prev.title != next.title {
54
- m.title = next.title
55
- }
56
-
57
- if prev.snippet != next.snippet {
58
- m.snippet = next.snippet
59
- }
60
-
61
- if prev.opacity != next.opacity {
62
- let opacity = Float(next.opacity ?? 1)
63
- m.opacity = opacity
64
- m.iconView?.alpha = CGFloat(opacity)
65
- }
66
-
67
- if prev.flat != next.flat {
68
- m.isFlat = next.flat ?? false
69
- }
70
-
71
- if prev.draggable != next.draggable {
72
- m.isDraggable = next.draggable ?? false
73
- }
74
-
75
- if prev.rotation != next.rotation {
76
- m.rotation = next.rotation ?? 0
77
- }
49
+ withCATransaction(disableActions: true) {
78
50
 
79
- if prev.zIndex != next.zIndex {
80
- m.zIndex = Int32(next.zIndex ?? 0)
81
- }
51
+ var tracksViewChanges = false
52
+ var tracksInfoWindowChanges = false
82
53
 
83
- if !prev.markerStyleEquals(next) {
84
- buildIconAsync(next.id, next) { img in
85
- m.tracksViewChanges = true
86
- m.icon = img
54
+ if !prev.coordinateEquals(next) {
55
+ m.position = next.coordinate.toCLLocationCoordinate2D()
56
+ }
87
57
 
88
- if prev.anchor?.x != next.anchor?.x || prev.anchor?.y != next.anchor?.y {
58
+ if !prev.markerStyleEquals(next) {
59
+ self.buildIconAsync(next) { img in
60
+ tracksViewChanges = true
61
+ m.icon = img
62
+
63
+ if !prev.anchorEquals(next) {
64
+ m.groundAnchor = CGPoint(
65
+ x: next.anchor?.x ?? 0.5,
66
+ y: next.anchor?.y ?? 1
67
+ )
68
+ }
69
+
70
+ if !prev.infoWindowAnchorEquals(next) {
71
+ m.infoWindowAnchor = CGPoint(
72
+ x: next.infoWindowAnchor?.x ?? 0.5,
73
+ y: next.infoWindowAnchor?.y ?? 0
74
+ )
75
+ }
76
+ }
77
+ } else {
78
+ if !prev.anchorEquals(next) {
89
79
  m.groundAnchor = CGPoint(
90
80
  x: next.anchor?.x ?? 0.5,
91
81
  y: next.anchor?.y ?? 1
92
82
  )
93
83
  }
94
84
 
95
- if prev.infoWindowAnchor?.x != next.infoWindowAnchor?.x
96
- || prev.infoWindowAnchor?.y != next.infoWindowAnchor?.y {
85
+ if !prev.infoWindowAnchorEquals(next) {
97
86
  m.infoWindowAnchor = CGPoint(
98
87
  x: next.infoWindowAnchor?.x ?? 0.5,
99
88
  y: next.infoWindowAnchor?.y ?? 0
100
89
  )
101
90
  }
91
+ }
102
92
 
103
- onMainAsync { [weak m] in
104
- try? await Task.sleep(nanoseconds: 250_000_000)
105
- m?.tracksViewChanges = false
106
- }
93
+ if prev.title != next.title {
94
+ tracksInfoWindowChanges = true
95
+ m.title = next.title
107
96
  }
108
- } else {
109
- if prev.anchor?.x != next.anchor?.x || prev.anchor?.y != next.anchor?.y {
110
- m.groundAnchor = CGPoint(
111
- x: next.anchor?.x ?? 0.5,
112
- y: next.anchor?.y ?? 1
113
- )
97
+
98
+ if prev.snippet != next.snippet {
99
+ tracksInfoWindowChanges = true
100
+ m.snippet = next.snippet
101
+ }
102
+
103
+ if prev.opacity != next.opacity {
104
+ let opacity = Float(next.opacity ?? 1)
105
+ m.opacity = opacity
106
+ m.iconView?.alpha = CGFloat(opacity)
107
+ }
108
+
109
+ if prev.flat != next.flat {
110
+ m.isFlat = next.flat ?? false
111
+ }
112
+
113
+ if prev.draggable != next.draggable {
114
+ m.isDraggable = next.draggable ?? false
115
+ }
116
+
117
+ if prev.rotation != next.rotation {
118
+ m.rotation = next.rotation ?? 0
119
+ }
120
+
121
+ if prev.zIndex != next.zIndex {
122
+ m.zIndex = Int32(next.zIndex ?? 0)
114
123
  }
115
124
 
116
- if prev.infoWindowAnchor?.x != next.infoWindowAnchor?.x
117
- || prev.infoWindowAnchor?.y != next.infoWindowAnchor?.y {
118
- m.infoWindowAnchor = CGPoint(
119
- x: next.infoWindowAnchor?.x ?? 0.5,
120
- y: next.infoWindowAnchor?.y ?? 0
125
+ if !prev.markerInfoWindowStyleEquals(next) {
126
+ m.tagData = MarkerTag(
127
+ id: next.id,
128
+ iconSvg: next.infoWindowIconSvg
121
129
  )
122
130
  }
131
+
132
+ if tracksViewChanges {
133
+ m.tracksViewChanges = tracksViewChanges
134
+ }
135
+ if tracksInfoWindowChanges {
136
+ m.tracksInfoWindowChanges = tracksInfoWindowChanges
137
+ }
138
+
139
+ if tracksViewChanges || tracksInfoWindowChanges {
140
+ onMain { [weak m] in
141
+ guard let m = m else { return }
142
+
143
+ if tracksViewChanges {
144
+ m.tracksViewChanges = false
145
+ }
146
+
147
+ if tracksInfoWindowChanges {
148
+ m.tracksInfoWindowChanges = false
149
+ }
150
+ }
151
+ }
123
152
  }
124
153
  }
125
154
 
126
- @MainActor
127
155
  func buildIconAsync(
128
- _ id: String,
129
156
  _ m: RNMarker,
130
157
  onReady: @escaping (UIImage?) -> Void
131
158
  ) {
132
- tasks[id]?.cancel()
159
+ tasks[m.id]?.cancel()
133
160
 
134
161
  if m.iconSvg == nil {
135
162
  onReady(nil)
@@ -142,12 +169,15 @@ final class MapMarkerBuilder {
142
169
  return
143
170
  }
144
171
 
172
+ let scale = UIScreen.main.scale
173
+
145
174
  let task = Task(priority: .userInitiated) { [weak self] in
146
175
  guard let self else { return }
147
- defer { self.tasks.removeValue(forKey: id) }
176
+ defer {
177
+ Task { @MainActor in self.tasks.removeValue(forKey: m.id) }
178
+ }
148
179
 
149
- let scale = UIScreen.main.scale
150
- let img = await self.renderUIImage(m, scale)
180
+ let img = self.renderUIImage(m, scale)
151
181
  guard let img, !Task.isCancelled else { return }
152
182
 
153
183
  self.iconCache.setObject(img, forKey: key)
@@ -158,7 +188,7 @@ final class MapMarkerBuilder {
158
188
  }
159
189
  }
160
190
 
161
- tasks[id] = task
191
+ tasks[m.id] = task
162
192
  }
163
193
 
164
194
  @MainActor
@@ -178,9 +208,42 @@ final class MapMarkerBuilder {
178
208
  }
179
209
 
180
210
  @MainActor
181
- private func renderUIImage(_ m: RNMarker, _ scale: CGFloat) async -> UIImage? {
182
- guard let iconSvg = m.iconSvg,
183
- let data = iconSvg.svgString.data(using: .utf8)
211
+ func buildInfoWindow(iconSvg: RNMarkerSvg?) -> UIImageView? {
212
+ guard let iconSvg = iconSvg else {
213
+ return nil
214
+ }
215
+
216
+ guard let data = iconSvg.svgString.data(using: .utf8),
217
+ let svgImg = SVGKImage(data: data)
218
+ else {
219
+ return nil
220
+ }
221
+
222
+ let size = CGSize(
223
+ width: max(1, CGFloat(iconSvg.width)),
224
+ height: max(1, CGFloat(iconSvg.height))
225
+ )
226
+
227
+ svgImg.size = size
228
+
229
+ guard let finalImage = SVGKExporterUIImage.export(asUIImage: svgImg) else {
230
+ svgImg.clear()
231
+ return nil
232
+ }
233
+ svgImg.clear()
234
+
235
+ let imageView = UIImageView(image: finalImage)
236
+ imageView.frame = CGRect(origin: .zero, size: size)
237
+ imageView.contentMode = .scaleAspectFit
238
+ imageView.backgroundColor = .clear
239
+
240
+ return imageView
241
+ }
242
+
243
+ private func renderUIImage(_ m: RNMarker, _ scale: CGFloat) -> UIImage? {
244
+ guard
245
+ let iconSvg = m.iconSvg,
246
+ let data = iconSvg.svgString.data(using: .utf8)
184
247
  else { return nil }
185
248
 
186
249
  let size = CGSize(
@@ -188,28 +251,21 @@ final class MapMarkerBuilder {
188
251
  height: max(1, CGFloat(iconSvg.height))
189
252
  )
190
253
 
191
- return await Task.detached(priority: .userInitiated) {
192
- autoreleasepool {
193
- guard let svgImg = SVGKImage(data: data) else { return nil }
194
- svgImg.size = size
254
+ return autoreleasepool { () -> UIImage? in
255
+ guard !Task.isCancelled else { return nil }
256
+ guard let svgImg = SVGKImage(data: data) else { return nil }
195
257
 
196
- guard !Task.isCancelled else { return nil }
197
- guard let base = svgImg.uiImage else { return nil }
258
+ svgImg.size = size
198
259
 
199
- if let cg = base.cgImage {
200
- return UIImage(cgImage: cg, scale: scale, orientation: .up)
201
- }
202
- guard !Task.isCancelled else { return nil }
203
- let fmt = UIGraphicsImageRendererFormat.default()
204
- fmt.opaque = false
205
- fmt.scale = scale
206
- guard !Task.isCancelled else { return nil }
207
- let renderer = UIGraphicsImageRenderer(size: size, format: fmt)
208
- return renderer.image { _ in
209
- base.draw(in: CGRect(origin: .zero, size: size))
210
- }
260
+ guard !Task.isCancelled else {
261
+ svgImg.clear()
262
+ return nil
211
263
  }
212
- }.value
264
+
265
+ let uiImage = SVGKExporterUIImage.export(asUIImage: svgImg)
266
+ svgImg.clear()
267
+ return uiImage
268
+ }
213
269
  }
214
270
 
215
271
  }
@@ -3,13 +3,7 @@ import GoogleMaps
3
3
  final class MapPolygonBuilder {
4
4
  @MainActor
5
5
  func build(_ p: RNPolygon) -> GMSPolygon {
6
- let path = GMSMutablePath()
7
- p.coordinates.forEach {
8
- path.add(
9
- $0.toCLLocationCoordinate2D()
10
- )
11
- }
12
-
6
+ let path = p.coordinates.toGMSPath()
13
7
  let pg = GMSPolygon(path: path)
14
8
 
15
9
  p.fillColor.map { pg.fillColor = $0.toUIColor() }
@@ -17,13 +11,7 @@ final class MapPolygonBuilder {
17
11
  p.strokeWidth.map { pg.strokeWidth = CGFloat($0) }
18
12
  p.pressable.map { pg.isTappable = $0 }
19
13
  p.geodesic.map { pg.geodesic = $0 }
20
- p.holes.map {
21
- pg.holes = $0.map { hole in
22
- let path = GMSMutablePath()
23
- hole.coordinates.forEach { path.add($0.toCLLocationCoordinate2D()) }
24
- return path
25
- }
26
- }
14
+ pg.holes = p.holes.toMapPolygonHoles()
27
15
  p.zIndex.map { pg.zIndex = Int32($0) }
28
16
 
29
17
  return pg
@@ -31,35 +19,12 @@ final class MapPolygonBuilder {
31
19
 
32
20
  @MainActor
33
21
  func update(_ prev: RNPolygon, _ next: RNPolygon, _ pg: GMSPolygon) {
34
- let coordsChanged =
35
- prev.coordinates.count != next.coordinates.count
36
- || !zip(prev.coordinates, next.coordinates).allSatisfy {
37
- $0.latitude == $1.latitude && $0.longitude == $1.longitude
38
- }
39
-
40
- if coordsChanged {
41
- let path = GMSMutablePath()
42
- next.coordinates.forEach { path.add($0.toCLLocationCoordinate2D()) }
43
- pg.path = path
22
+ if !prev.coordinatesEquals(next) {
23
+ pg.path = next.coordinates.toGMSPath()
44
24
  }
45
25
 
46
- let prevHoles = prev.holes ?? []
47
- let nextHoles = next.holes ?? []
48
- let holesChanged =
49
- prevHoles.count != nextHoles.count
50
- || !zip(prevHoles, nextHoles).allSatisfy { a, b in
51
- a.coordinates.count == b.coordinates.count
52
- && zip(a.coordinates, b.coordinates).allSatisfy {
53
- $0.latitude == $1.latitude && $0.longitude == $1.longitude
54
- }
55
- }
56
-
57
- if holesChanged {
58
- pg.holes = nextHoles.map { hole in
59
- let path = GMSMutablePath()
60
- hole.coordinates.forEach { path.add($0.toCLLocationCoordinate2D()) }
61
- return path
62
- }
26
+ if !prev.holesEquals(next) {
27
+ pg.holes = next.holes.toMapPolygonHoles()
63
28
  }
64
29
 
65
30
  if prev.fillColor != next.fillColor {
@@ -25,16 +25,8 @@ final class MapPolylineBuilder {
25
25
 
26
26
  @MainActor
27
27
  func update(_ prev: RNPolyline, _ next: RNPolyline, _ pl: GMSPolyline) {
28
- let coordsChanged =
29
- prev.coordinates.count != next.coordinates.count
30
- || !zip(prev.coordinates, next.coordinates).allSatisfy {
31
- $0.latitude == $1.latitude && $0.longitude == $1.longitude
32
- }
33
-
34
- if coordsChanged {
35
- let path = GMSMutablePath()
36
- next.coordinates.forEach { path.add($0.toCLLocationCoordinate2D()) }
37
- pl.path = path
28
+ if !prev.coordinatesEquals(next) {
29
+ pl.path = next.coordinates.toGMSPath()
38
30
  }
39
31
 
40
32
  if prev.width != next.width {
@@ -41,6 +41,9 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec {
41
41
  initialProps?.camera.map {
42
42
  options.camera = $0.toGMSCameraPosition(current: nil)
43
43
  }
44
+ initialProps?.backgroundColor.map {
45
+ options.backgroundColor = $0.toUIColor()
46
+ }
44
47
  impl.initMapView(googleMapOptions: options)
45
48
  }
46
49
  }
@@ -128,26 +131,26 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec {
128
131
  )
129
132
 
130
133
  let removed = Set(prevById.keys).subtracting(nextById.keys)
131
- withCATransaction(disableActions: true) {
132
134
 
133
- removed.forEach {
134
- self.impl.removeMarker(id: $0)
135
- self.markerBuilder.cancelIconTask($0)
136
- }
135
+ removed.forEach {
136
+ self.impl.removeMarker(id: $0)
137
+ self.markerBuilder.cancelIconTask($0)
138
+ }
137
139
 
138
- for (id, next) in nextById {
139
- if let prev = prevById[id] {
140
- if !prev.markerEquals(next) {
141
- self.impl.updateMarker(id: id) { m in
142
- self.markerBuilder.update(prev, next, m)
143
- }
144
- }
145
- } else {
146
- self.markerBuilder.buildIconAsync(next.id, next) { icon in
147
- let marker = self.markerBuilder.build(next, icon: icon)
148
- self.impl.addMarker(id: id, marker: marker)
140
+ for (id, next) in nextById {
141
+ if let prev = prevById[id] {
142
+ if !prev.markerEquals(next) {
143
+ self.impl.updateMarker(id: id) { [weak self] m in
144
+ guard let self else { return }
145
+ self.markerBuilder.update(prev, next, m)
149
146
  }
150
147
  }
148
+ } else {
149
+ self.markerBuilder.buildIconAsync(next) { [weak self] icon in
150
+ guard let self else { return }
151
+ let marker = self.markerBuilder.build(next, icon: icon)
152
+ self.impl.addMarker(id: id, marker: marker)
153
+ }
151
154
  }
152
155
  }
153
156
  }
@@ -171,7 +174,8 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec {
171
174
  for (id, next) in nextById {
172
175
  if let prev = prevById[id] {
173
176
  if !prev.polylineEquals(next) {
174
- impl.updatePolyline(id: id) { pl in
177
+ impl.updatePolyline(id: id) { [weak self] pl in
178
+ guard let self else { return }
175
179
  self.polylineBuilder.update(prev, next, pl)
176
180
  }
177
181
  }
@@ -203,7 +207,8 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec {
203
207
  for (id, next) in nextById {
204
208
  if let prev = prevById[id] {
205
209
  if !prev.polygonEquals(next) {
206
- impl.updatePolygon(id: id) { pg in
210
+ impl.updatePolygon(id: id) { [weak self] pg in
211
+ guard let self else { return }
207
212
  self.polygonBuilder.update(prev, next, pg)
208
213
  }
209
214
  }
@@ -232,7 +237,8 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec {
232
237
  for (id, next) in nextById {
233
238
  if let prev = prevById[id] {
234
239
  if !prev.circleEquals(next) {
235
- impl.updateCircle(id: id) { circle in
240
+ impl.updateCircle(id: id) { [weak self] circle in
241
+ guard let self else { return }
236
242
  self.circleBuilder.update(prev, next, circle)
237
243
  }
238
244
  }
@@ -325,7 +331,7 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec {
325
331
  didSet { impl.onMapReady = onMapReady }
326
332
  }
327
333
  @MainActor
328
- var onMapLoaded: ((RNRegion, RNCamera) -> Void)? {
334
+ var onMapLoaded: ((RNRegion, RNCameraChange) -> Void)? {
329
335
  didSet { impl.onMapLoaded = onMapLoaded }
330
336
  }
331
337
  @MainActor
@@ -349,31 +355,31 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec {
349
355
  didSet { impl.onPoiPress = onPoiPress }
350
356
  }
351
357
  @MainActor
352
- var onMarkerPress: ((String?) -> Void)? {
358
+ var onMarkerPress: ((String) -> Void)? {
353
359
  didSet { impl.onMarkerPress = onMarkerPress }
354
360
  }
355
361
  @MainActor
356
- var onPolylinePress: ((String?) -> Void)? {
362
+ var onPolylinePress: ((String) -> Void)? {
357
363
  didSet { impl.onPolylinePress = onPolylinePress }
358
364
  }
359
365
  @MainActor
360
- var onPolygonPress: ((String?) -> Void)? {
366
+ var onPolygonPress: ((String) -> Void)? {
361
367
  didSet { impl.onPolygonPress = onPolygonPress }
362
368
  }
363
369
  @MainActor
364
- var onCirclePress: ((String?) -> Void)? {
370
+ var onCirclePress: ((String) -> Void)? {
365
371
  didSet { impl.onCirclePress = onCirclePress }
366
372
  }
367
373
  @MainActor
368
- var onMarkerDragStart: ((String?, RNLatLng) -> Void)? {
374
+ var onMarkerDragStart: ((String, RNLatLng) -> Void)? {
369
375
  didSet { impl.onMarkerDragStart = onMarkerDragStart }
370
376
  }
371
377
  @MainActor
372
- var onMarkerDrag: ((String?, RNLatLng) -> Void)? {
378
+ var onMarkerDrag: ((String, RNLatLng) -> Void)? {
373
379
  didSet { impl.onMarkerDrag = onMarkerDrag }
374
380
  }
375
381
  @MainActor
376
- var onMarkerDragEnd: ((String?, RNLatLng) -> Void)? {
382
+ var onMarkerDragEnd: ((String, RNLatLng) -> Void)? {
377
383
  didSet { impl.onMarkerDragEnd = onMarkerDragEnd }
378
384
  }
379
385
  @MainActor
@@ -385,15 +391,15 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec {
385
391
  didSet { impl.onIndoorLevelActivated = onIndoorLevelActivated }
386
392
  }
387
393
  @MainActor
388
- var onInfoWindowPress: ((String?) -> Void)? {
394
+ var onInfoWindowPress: ((String) -> Void)? {
389
395
  didSet { impl.onInfoWindowPress = onInfoWindowPress }
390
396
  }
391
397
  @MainActor
392
- var onInfoWindowClose: ((String?) -> Void)? {
398
+ var onInfoWindowClose: ((String) -> Void)? {
393
399
  didSet { impl.onInfoWindowClose = onInfoWindowClose }
394
400
  }
395
401
  @MainActor
396
- var onInfoWindowLongPress: ((String?) -> Void)? {
402
+ var onInfoWindowLongPress: ((String) -> Void)? {
397
403
  didSet { impl.onInfoWindowLongPress = onInfoWindowLongPress }
398
404
  }
399
405
  @MainActor
@@ -405,18 +411,28 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec {
405
411
  didSet { impl.onMyLocationButtonPress = onMyLocationButtonPress }
406
412
  }
407
413
  @MainActor
408
- var onCameraChangeStart: ((RNRegion, RNCamera, Bool) -> Void)? {
414
+ var onCameraChangeStart: ((RNRegion, RNCameraChange, Bool) -> Void)? {
409
415
  didSet { impl.onCameraChangeStart = onCameraChangeStart }
410
416
  }
411
417
  @MainActor
412
- var onCameraChange: ((RNRegion, RNCamera, Bool) -> Void)? {
418
+ var onCameraChange: ((RNRegion, RNCameraChange, Bool) -> Void)? {
413
419
  didSet { impl.onCameraChange = onCameraChange }
414
420
  }
415
421
  @MainActor
416
- var onCameraChangeComplete: ((RNRegion, RNCamera, Bool) -> Void)? {
422
+ var onCameraChangeComplete: ((RNRegion, RNCameraChange, Bool) -> Void)? {
417
423
  didSet { impl.onCameraChangeComplete = onCameraChangeComplete }
418
424
  }
419
425
 
426
+ @MainActor
427
+ func showMarkerInfoWindow(id: String) {
428
+ impl.showMarkerInfoWindow(id: id)
429
+ }
430
+
431
+ @MainActor
432
+ func hideMarkerInfoWindow(id: String) {
433
+ impl.hideMarkerInfoWindow(id: id)
434
+ }
435
+
420
436
  @MainActor
421
437
  func setCamera(camera: RNCamera, animated: Bool?, durationMs: Double?) {
422
438
  let cam = camera.toGMSCameraPosition(current: impl.currentCamera)
@@ -1,8 +1,8 @@
1
1
  import GoogleMaps
2
2
 
3
3
  extension GMSCameraPosition {
4
- func toRNCamera() -> RNCamera {
5
- return RNCamera(
4
+ func toRNCamera() -> RNCameraChange {
5
+ return RNCameraChange(
6
6
  center: target.toRNLatLng(),
7
7
  zoom: Double(zoom),
8
8
  bearing: bearing,
@@ -0,0 +1,93 @@
1
+ import GoogleMaps
2
+ import Foundation
3
+
4
+ protocol MapObjectTag {
5
+ var id: String { get }
6
+ }
7
+
8
+ struct MarkerTag: MapObjectTag {
9
+ let id: String
10
+ let iconSvg: RNMarkerSvg?
11
+ init(id: String, iconSvg: RNMarkerSvg? = nil) {
12
+ self.id = id
13
+ self.iconSvg = iconSvg
14
+ }
15
+ }
16
+
17
+ struct PolylineTag: MapObjectTag { let id: String }
18
+ struct PolygonTag: MapObjectTag { let id: String }
19
+ struct CircleTag: MapObjectTag { let id: String }
20
+
21
+ extension GMSMarker {
22
+ var tagData: MarkerTag {
23
+ get {
24
+ if let tag = userData as? MarkerTag {
25
+ return tag
26
+ } else {
27
+ print("[MapTag] Marker without tag detected at \(position)")
28
+ let fallback = MarkerTag(id: "unknown")
29
+ userData = fallback
30
+ return fallback
31
+ }
32
+ }
33
+ set {
34
+ userData = newValue
35
+ }
36
+ }
37
+
38
+ var idTag: String { tagData.id }
39
+ }
40
+
41
+ extension GMSPolyline {
42
+ var tagData: PolylineTag {
43
+ get {
44
+ if let tag = userData as? PolylineTag {
45
+ return tag
46
+ } else {
47
+ print("[MapTag] Polyline without tag detected")
48
+ let fallback = PolylineTag(id: "unknown")
49
+ userData = fallback
50
+ return fallback
51
+ }
52
+ }
53
+ set { userData = newValue }
54
+ }
55
+
56
+ var idTag: String { tagData.id }
57
+ }
58
+
59
+ extension GMSPolygon {
60
+ var tagData: PolygonTag {
61
+ get {
62
+ if let tag = userData as? PolygonTag {
63
+ return tag
64
+ } else {
65
+ print("[MapTag] Polygon without tag detected")
66
+ let fallback = PolygonTag(id: "unknown")
67
+ userData = fallback
68
+ return fallback
69
+ }
70
+ }
71
+ set { userData = newValue }
72
+ }
73
+
74
+ var idTag: String { tagData.id }
75
+ }
76
+
77
+ extension GMSCircle {
78
+ var tagData: CircleTag {
79
+ get {
80
+ if let tag = userData as? CircleTag {
81
+ return tag
82
+ } else {
83
+ print("[MapTag] Circle without tag detected")
84
+ let fallback = CircleTag(id: "unknown")
85
+ userData = fallback
86
+ return fallback
87
+ }
88
+ }
89
+ set { userData = newValue }
90
+ }
91
+
92
+ var idTag: String { tagData.id }
93
+ }