iOS / Apple Maps: custom Marker is mispositioned when its content changes size (New Architecture)

Author: spendresCreated Jul 28, 2026Updated Sep 14, 2026

Summary

On Apple Maps (PROVIDER_DEFAULT) with the New Architecture, a custom <Marker> whose child changes size ends up mispositioned — it stays placed as though it still had a previous, larger size. Markers whose child never changes size are placed correctly, which is why this is easy to miss.

The cause is that AIRMapMarker's bounds are derived from the first subview and only ever grow:

https://github.com/react-native-maps/react-native-maps/blob/v1.27.2/ios/AirMaps/AIRMapMarker.m#L421-L433

objc
- (void)layoutSubviews
{
    [super layoutSubviews];
    CGRect reactFrame = self.frame;
    UIView *firstSubView = self.subviews.firstObject;
    if (firstSubView && (CGRectGetWidth(firstSubView.frame) > CGRectGetWidth(reactFrame) ||
                         CGRectGetHeight(firstSubView.frame) > CGRectGetHeight(reactFrame))) {
        reactFrame = firstSubView.frame;   // <- taken ONLY when larger
    }
    [self reactSetFrame:reactFrame];
}

reactSetFrame: publishes that size as self.bounds, and MapKit centres an annotation view on its bounds. So once a marker has been wide, it keeps the wide bounds, and its visible content is drawn off-centre by half the difference — permanently, since the size never comes back down.

Environment

react-native-maps 1.27.2
react-native 0.85.3 (New Architecture / Fabric)
Expo SDK 56
Platform iOS Simulator, PROVIDER_DEFAULT (Apple Maps)

Google Maps on iOS and Android are not affected — they honour the fractional anchor prop, which Apple Maps ignores.

Reproduction

Render a <Marker> whose child's width depends on state, and change that state:

typescript
<Marker coordinate={coord} tracksViewChanges={tracks}>
  <View style={{ backgroundColor: '#000', paddingHorizontal: 10, height: 28 }}>
    <Text style={{ color: '#fff' }}>{label}</Text>
  </View>
</Marker>

Switch label from a long string to a short one. The marker keeps the wider bounds and the visible pill sits off to one side of the coordinate. Switching back to a long label appears to "fix" it, which makes it read like an intermittent bug.

Measurements

Two labels of the same design, on the same coordinate:

  • content ≈148pt wide → coordinate fell ≈66pt from the content's left edge
  • content ≈272pt wide → coordinate fell ≈64pt from the content's left edge

A near-constant absolute offset across very different content widths — the signature of bounds that are inherited rather than measured.

Two fixes that do NOT work (both tested on device)

1. Deleting -layoutSubviews and -setFrame: — i.e. matching what v2.0.0-beta.15 already does — results in no marker rendering at all on the New Architecture.

The reason matters for 2.x: RNMapsMarkerView.mm (the Fabric component view) has no updateLayoutMetrics, no layoutSubviews and no setFrame override, and never assigns _view.frame; it only mounts children onto the legacy view via insertReactSubview:. So under Fabric, -layoutSubviews is the only code that ever sizes the marker. v2.0.0-beta.15 can delete it because its marker is Paper-only (ios/Maps/ contains RNMMapMarker + manager and no Fabric component view), where the UIManager calls reactSetFrame: directly.

2. Relaxing the condition to take the subview's frame unconditionally makes the marker drift upward off the screen — reactSetFrame: then runs on every layout pass, and each pass with a non-zero dy subtracts from self.center.y.

So the fix is not simply "delete it" or "always take it"; the interaction between -layoutSubviews, reactSetFrame:'s center rewrite, and Fabric's lack of a layout override needs to be resolved together. I did not want to guess at that in a PR without maintainer input on the intended ownership of layout.

Workaround for users

Give the marker's child a constant size, and vary only what is drawn inside it. A fixed-size <Svg> works well, because it is a single native view whose dimensions never change:

typescript
// canvas is CONSTANT for every label; only the pill drawn inside it varies
<Marker coordinate={coord} tracksViewChanges={tracks}>
  <Svg width={456} height={72} viewBox="0 0 456 72">
    <Rect x={236} y={22} width={pillWidth} height={28} rx={14} fill="rgba(0,0,0,0.72)" />
    <SvgText x={248} y={36} fontSize={14} fill="#fff" alignmentBaseline="central">
      {label}
    </SvgText>
  </Svg>
</Marker>

Because the canvas never changes size, reactSetFrame: computes dy = 0, bounds never ratchet, and MapKit's centring is stable. Any offset you need (e.g. placing the content beside the coordinate rather than on it) can be baked into where you draw inside the canvas — no anchor or centerOffset required, which is convenient given anchor is a no-op on Apple Maps.

This is what the library's own MKMarkerAnnotationView-style pins do implicitly: fixed-size children have never exhibited the problem.

Note for 2.x

reactSetFrame:'s center rewrite is byte-identical in 1.27.2, 1.28.0, 1.29.0 and 2.0.0-beta.15. The two methods above are gone in 2.0, which likely resolves the ratchet for the Paper marker — but if a Fabric marker component lands in 2.x, whatever sizes the annotation will need to be re-established, or the same "no marker at all" result will follow. I have not been able to test 2.0 directly.

Happy to open a PR if maintainers can say where layout should be owned under Fabric — the fix looks small once that is decided.

Source: react-native-maps/react-native-maps