Sitelet https://github.com/rnmapbox/maps/pull/4288
Skip to content

fix(ios): resolve pending layer waiters - #4288

Open
LukasB-DEV wants to merge 1 commit into
rnmapbox:mainfrom
LukasB-DEV:codex/fix-ios-layer-waiters
Open

LukasB-DEV wants to merge 1 commit into
rnmapbox:mainfrom
LukasB-DEV:codex/fix-ios-layer-waiters

Conversation

@LukasB-DEV

Copy link
Copy Markdown

Description

On iOS, a layer mounted with belowLayerID before its anchor layer exists is queued by waitForLayerWithID. However, RNMBXMapView.layerAdded(_:) was left as a TODO, so the queued callbacks were never invoked after the anchor layer was added.

As a result, the dependent layer remained absent from the map.

This change removes and invokes the callbacks waiting for the newly added layer. It mirrors the existing Android implementation and also prevents fulfilled callbacks from remaining retained in layerWaiters.

This is a general iOS layer-ordering issue and is not specific to an application integration.

No linked issue.

Reproduction and verification

Tested against unmodified main

Environment:

  • @rnmapbox/maps 10.3.5
  • Mapbox Maps SDK v11.23.1
  • React Native New Architecture
  • iPhone 17 Pro Simulator
  • iOS 26.5
  • Xcode 26.6
  • Node.js 22.16.0

Before the fix:

  • FAIL: WAITING LAYER MISSING
  • The magenta anchor layer was visible.
  • The blue layer waiting for belowLayerID="anchor-layer" was absent.

After the fix:

  • PASS: WAITING LAYER RENDERED
  • Both the blue waiting layer and the magenta anchor layer were visible.

Checklist

  • I've read CONTRIBUTING.md
  • I updated the doc/other generated code with running yarn generate in the root folder
    • yarn generate completed successfully and produced no changes.
  • I have tested the fix on the /example app.
    • In V11 mode/ios
    • In New Architecture mode/ios
    • In V11 mode/android — not applicable to this iOS-only change
    • In New Architecture mode/android — not applicable to this iOS-only change
  • I added/updated a sample — not applicable; this is a bug fix and the temporary reproducer was intentionally kept out of the final commit.

Screenshot OR Video

Before — unfixed main:

image

After — commit 9af2571:

image

Tests

  • yarn generate — passed, generated tree remained unchanged
  • yarn test — passed, 22 test suites and 112 tests
  • ESLint — 0 errors; existing repository warnings only
  • yarn typecheck — passed
  • iOS Example Debug build — passed before and after the fix
  • Reproducer ESLint check — passed
  • git diff --check — passed

Component to reproduce the issue you're fixing

BugReportExample.js
import React from 'react';
import { Text, View } from 'react-native';
import {
  Camera,
  LineLayer,
  MapView,
  ShapeSource,
  StyleURL,
} from '@rnmapbox/maps';

const routes = {
  type: 'FeatureCollection',
  features: [
    {
      type: 'Feature',
      properties: { kind: 'waiting' },
      geometry: {
        type: 'LineString',
        coordinates: [
          [-74.012, 40.716],
          [-74.0, 40.716],
        ],
      },
    },
    {
      type: 'Feature',
      properties: { kind: 'anchor' },
      geometry: {
        type: 'LineString',
        coordinates: [
          [-74.012, 40.713],
          [-74.0, 40.713],
        ],
      },
    },
  ],
};

const styles = {
  map: { flex: 1 },
  status: {
    position: 'absolute',
    top: 60,
    left: 12,
    right: 12,
    padding: 12,
    backgroundColor: 'white',
  },
  statusText: { color: 'black', fontWeight: '700' },
};

class BugReportExample extends React.Component {
  state = { status: 'CHECKING' };

  checkWaitingLayer = async () => {
    try {
      const result = await this.map.queryRenderedFeaturesInRect([], null, [
        'waiting-layer',
      ]);

      this.setState({
        status: result?.features?.length
          ? 'PASS: WAITING LAYER RENDERED'
          : 'FAIL: WAITING LAYER MISSING',
      });
    } catch {
      this.setState({ status: 'FAIL: WAITING LAYER MISSING' });
    }
  };

  render() {
    return (
      <View style={styles.map}>
        <MapView
          ref={(map) => {
            this.map = map;
          }}
          style={styles.map}
          styleURL={StyleURL.Light}
          onMapIdle={this.checkWaitingLayer}
        >
          <Camera
            defaultSettings={{
              centerCoordinate: [-74.006, 40.7145],
              zoomLevel: 14,
            }}
          />

          <ShapeSource id="layer-waiter-reproducer" shape={routes}>
            <LineLayer
              id="waiting-layer"
              belowLayerID="anchor-layer"
              filter={['==', ['get', 'kind'], 'waiting']}
              style={{ lineColor: '#0066ff', lineWidth: 12 }}
            />

            <LineLayer
              id="anchor-layer"
              filter={['==', ['get', 'kind'], 'anchor']}
              style={{ lineColor: '#ff0066', lineWidth: 12 }}
            />
          </ShapeSource>
        </MapView>

        <View style={styles.status}>
          <Text testID="layer-waiter-status" style={styles.statusText}>
            {this.state.status}
          </Text>
        </View>
      </View>
    );
  }
}

export default BugReportExample;

@LukasB-DEV
LukasB-DEV had a problem deploying to CI with Mapbox Tokens August 27, 2026 08:15 — with GitHub Actions Failure
@LukasB-DEV
LukasB-DEV had a problem deploying to CI with Mapbox Tokens August 27, 2026 08:15 — with GitHub Actions Failure
@LukasB-DEV
LukasB-DEV had a problem deploying to CI with Mapbox Tokens August 27, 2026 08:15 — with GitHub Actions Failure
R4ULtv added a commit to R4ULtv/rail-radar that referenced this pull request Sep 26, 2026
A map button, grouped with the locate button on one surface like Apple Maps' controls, opens a sheet to pick the simple map (Mapbox's light and dark styles, still the default) or the street map (Mapbox Standard with its day or night light preset, without transit labels or 3D objects). Each option has a preview of Milano Centrale in that style and theme, and the choice is saved to map-style.json. The sheet has a centered title and the map credits, which move out of the search sheet into a shared component.

Standard keeps its Mapbox Streets tiles inside its import, so the street map loads them as a source of its own for the railway lines, and the lines, icons and labels use full emissive strength so its night lighting doesn't darken them. A style switch adds the railway lines again on top of the stations, so they're pinned below the station layers with belowLayerID and mounted after them, since rnmapbox 10.3.5 on iOS never adds a layer that waits for one that isn't on the map yet (rnmapbox/maps#4288).

mfazekas commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

@LukasB-DEV thanks for the fix, and sorry it took so long to get to it.

Could you merge the latest main into your branch? We changed how CI runs for fork PRs (#4298), so the checks here can't run until the branch is updated. It merges cleanly, so it should be a quick one. Once CI is green we can get this in.

This branch had an error being deployed

1 failed deployment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants