Skip to content

Commit 9926c45

Browse files
authored
fix(ios): prevent crash on Google Maps animated gradient polyline (#84)
* fix(ios): prevent crash on Google Maps gradient polyline updates Clear GMSPolyline spans before swapping a shorter path so Google Maps never rebuilds span models against missing segments (GMSModelStyleBuilder couldNotInstantiate -> EXC_BAD_ACCESS). Guard zero-segment paths and drop duplicate adjacent coordinates in both animated and static updates. Gradient rendering is preserved. * test(example): drive gradient polyline from truck position Slice the animated route ahead of the moving truck so the polyline coordinates mutate mid-animation, mirroring the live-route scenario from the Mover crash. Bumps example Podfile.lock to beta.16.
1 parent fb01222 commit 9926c45

4 files changed

Lines changed: 60 additions & 19 deletions

File tree

example/bare/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ PODS:
66
- hermes-engine (250829098.0.10):
77
- hermes-engine/Pre-built (= 250829098.0.10)
88
- hermes-engine/Pre-built (250829098.0.10)
9-
- LuggMaps (1.0.0-beta.14):
9+
- LuggMaps (1.0.0-beta.16):
1010
- GoogleMaps
1111
- hermes-engine
1212
- RCTRequired
@@ -2427,7 +2427,7 @@ SPEC CHECKSUMS:
24272427
FBLazyVector: 24e62c765683b8d89006a88a2c8f5cf019f0074d
24282428
GoogleMaps: 0608099d4870cac8754bdba9b6953db543432438
24292429
hermes-engine: a43fcac5345a0a468667778019547c5fd282c6e2
2430-
LuggMaps: b69f00e83f91bd2e00fb32341dff2d2cc57e794a
2430+
LuggMaps: 7f6539e8b1f2d1c328f6c9634cc0544a78b39076
24312431
RCTDeprecation: a4c521821fab57cbb125b36effe84d897d0dfa12
24322432
RCTRequired: 9f3a7e5645d4bc3f551593de7550bb66ab6e42bc
24332433
RCTSwiftUI: 239ed2eb9e73de5a6f518810630f0c95e01c8702

example/shared/src/components/CrewMarker.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface CrewMarkerProps {
2121
loaded?: boolean;
2222
speed?: number;
2323
zoom?: number;
24+
onSegment?: (index: number) => void;
2425
}
2526

2627
const getBearing = (from: Coordinate, to: Coordinate, currentBearing = 0) => {
@@ -57,7 +58,10 @@ export const CrewMarker = ({
5758
loaded = false,
5859
speed = 1,
5960
zoom = BASE_ZOOM,
61+
onSegment,
6062
}: CrewMarkerProps) => {
63+
const onSegmentRef = useRef(onSegment);
64+
onSegmentRef.current = onSegment;
6165
const latitude = useSharedValue(route[0]?.latitude ?? 0);
6266
const longitude = useSharedValue(route[0]?.longitude ?? 0);
6367
const bearingValue = useSharedValue(0);
@@ -102,6 +106,8 @@ export const CrewMarker = ({
102106
return;
103107
}
104108

109+
onSegmentRef.current?.(index);
110+
105111
const from = route[index]!;
106112
const to = route[index + 1]!;
107113

example/shared/src/components/Map.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,12 @@ export const Map = memo(
310310
[markers]
311311
);
312312

313+
const [truckIndex, setTruckIndex] = useState(0);
314+
const routeAhead = useMemo(
315+
() => smoothedRoute.slice(truckIndex),
316+
[smoothedRoute, truckIndex]
317+
);
318+
313319
const centerPinStyle = useAnimatedStyle(() => {
314320
const bottom = animatedPosition
315321
? screenHeight - animatedPosition.value
@@ -353,8 +359,12 @@ export const Map = memo(
353359
provider === 'apple' || Platform.OS === 'android'
354360
)
355361
)}
356-
<Route coordinates={smoothedRoute} />
357-
<CrewMarker route={smoothedRoute} zoom={zoom} />
362+
<Route coordinates={routeAhead} />
363+
<CrewMarker
364+
route={smoothedRoute}
365+
zoom={zoom}
366+
onSegment={setTruckIndex}
367+
/>
358368
<Polygon
359369
coordinates={CIRCLE_COORDS}
360370
holes={CIRCLE_HOLES}

ios/core/GMSPolylineAnimator.m

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,25 @@ - (void)update {
148148
}
149149

150150
GMSMutablePath *path = [GMSMutablePath path];
151-
for (CLLocation *location in self.coordinates) {
152-
[path addCoordinate:location.coordinate];
151+
CLLocationCoordinate2D lastCoord = self.coordinates.firstObject.coordinate;
152+
[path addCoordinate:lastCoord];
153+
for (NSUInteger i = 1; i < self.coordinates.count; i++) {
154+
CLLocationCoordinate2D coord = self.coordinates[i].coordinate;
155+
if (coord.latitude == lastCoord.latitude && coord.longitude == lastCoord.longitude) {
156+
continue;
157+
}
158+
[path addCoordinate:coord];
159+
lastCoord = coord;
153160
}
161+
162+
// Clear stale spans before swapping the path so Google Maps never rebuilds
163+
// span models against a path with fewer segments (GMSModelStyleBuilder
164+
// couldNotInstantiate -> EXC_BAD_ACCESS). Reassign spans once the new path is set.
165+
_polyline.spans = nil;
154166
_polyline.path = path;
155167

156-
if (self.strokeColors.count > 1) {
157-
_polyline.spans = [self createGradientSpans];
168+
if (path.count > 1 && self.strokeColors.count > 1) {
169+
_polyline.spans = [self createGradientSpansForSegmentCount:path.count - 1];
158170
} else {
159171
_polyline.strokeColor = self.strokeColors.firstObject ?: [UIColor blackColor];
160172
}
@@ -222,35 +234,44 @@ - (void)updateAnimatedPolyline {
222234
}
223235

224236
if (headDist <= tailDist) {
237+
_polyline.spans = nil;
225238
_polyline.path = [GMSMutablePath path];
226239
return;
227240
}
228241

229-
CGFloat visibleLength = headDist - tailDist;
230242
NSUInteger startIndex = [self indexForDistance:tailDist];
231243
NSUInteger endIndex = [self indexForDistance:headDist];
232244

233245
GMSMutablePath *path = [GMSMutablePath path];
234-
NSMutableArray<GMSStyleSpan *> *spans = [NSMutableArray array];
235246

236-
CLLocationCoordinate2D startCoord = [self coordinateAtDistance:tailDist];
237-
[path addCoordinate:startCoord];
247+
CLLocationCoordinate2D lastCoord = [self coordinateAtDistance:tailDist];
248+
[path addCoordinate:lastCoord];
238249

239250
for (NSUInteger i = startIndex + 1; i <= endIndex; i++) {
240-
[path addCoordinate:self.coordinates[i].coordinate];
251+
CLLocationCoordinate2D coord = self.coordinates[i].coordinate;
252+
if (coord.latitude == lastCoord.latitude && coord.longitude == lastCoord.longitude) {
253+
continue;
254+
}
255+
[path addCoordinate:coord];
256+
lastCoord = coord;
241257
}
242258

243259
CLLocationCoordinate2D endCoord = [self coordinateAtDistance:headDist];
244-
CLLocationCoordinate2D lastAdded =
245-
(endIndex < self.coordinates.count) ? self.coordinates[endIndex].coordinate : endCoord;
246-
if (endCoord.latitude != lastAdded.latitude || endCoord.longitude != lastAdded.longitude) {
260+
if (endCoord.latitude != lastCoord.latitude || endCoord.longitude != lastCoord.longitude) {
247261
[path addCoordinate:endCoord];
248262
}
249263

250264
NSUInteger pathCount = path.count;
251-
NSUInteger segmentCount = pathCount - 1;
265+
NSUInteger segmentCount = (pathCount > 1) ? pathCount - 1 : 0;
266+
267+
if (segmentCount == 0) {
268+
_polyline.spans = nil;
269+
_polyline.path = path;
270+
return;
271+
}
252272

253273
if (self.strokeColors.count <= 1) {
274+
_polyline.spans = nil;
254275
_polyline.path = path;
255276
_polyline.strokeColor = self.strokeColors.firstObject ?: [UIColor blackColor];
256277
return;
@@ -259,20 +280,24 @@ - (void)updateAnimatedPolyline {
259280
NSUInteger spanCount = MIN(segmentCount, kMaxAnimationSpans);
260281
double segmentsPerSpan = (double)segmentCount / spanCount;
261282

283+
NSMutableArray<GMSStyleSpan *> *spans = [NSMutableArray array];
262284
for (NSUInteger i = 0; i < spanCount; i++) {
263285
CGFloat gradientPos = ((CGFloat)i + 0.5) / spanCount;
264286
UIColor *color = [self colorAtGradientPosition:gradientPos];
265287
GMSStrokeStyle *style = [GMSStrokeStyle solidColor:color];
266288
[spans addObject:[GMSStyleSpan spanWithStyle:style segments:segmentsPerSpan]];
267289
}
268290

291+
// Clear stale spans before swapping the path so Google Maps never rebuilds
292+
// span models against a path with fewer segments (GMSModelStyleBuilder
293+
// couldNotInstantiate -> EXC_BAD_ACCESS). Reassign spans once the new path is set.
294+
_polyline.spans = nil;
269295
_polyline.path = path;
270296
_polyline.spans = spans;
271297
}
272298

273-
- (NSArray<GMSStyleSpan *> *)createGradientSpans {
299+
- (NSArray<GMSStyleSpan *> *)createGradientSpansForSegmentCount:(NSUInteger)segmentCount {
274300
NSMutableArray<GMSStyleSpan *> *spans = [NSMutableArray array];
275-
NSUInteger segmentCount = self.coordinates.count - 1;
276301
NSUInteger spanCount = MIN(segmentCount, kMaxGradientSpans);
277302
double segmentsPerSpan = (double)segmentCount / spanCount;
278303

0 commit comments

Comments
 (0)