Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: Map layers not loading after changing pitchEnabled on iOS #3791

Open
yunwi5 opened this issue Feb 24, 2025 · 0 comments
Open

[Bug]: Map layers not loading after changing pitchEnabled on iOS #3791

yunwi5 opened this issue Feb 24, 2025 · 0 comments
Labels
bug 🪲 Something isn't working

Comments

@yunwi5
Copy link

yunwi5 commented Feb 24, 2025

Mapbox Implementation

Mapbox

Mapbox Version

11.7.1

React Native Version

0.76.6

Platform

iOS

@rnmapbox/maps version

10.1.37

Standalone component to reproduce

/* eslint-disable react/prop-types */
import { EventEmitter } from 'events';
import Mapbox, { MapView as MapboxMapView } from '@rnmapbox/maps';
import { RegionPayload } from '@rnmapbox/maps/lib/typescript/src/components/MapView';
import { Feature, Point } from 'geojson';
import { debounce } from 'lodash';
import React, {
  FunctionComponent,
  RefObject,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import { useSharedValue } from 'react-native-reanimated';
import PropTypes from 'prop-types';

// Define Region using types imported from 'geojson'
export type Region = Feature<Point, RegionPayload>;

// Instead of using InferProps, we define our props explicitly.
export interface MapViewProps {
  onRegionIsChanging?: (region: Region) => void;
  onRegionDidChange?: (region: Region) => void;
  enableFollowMode?: boolean;
  children?: React.ReactNode;
}

export type MapViewProperties = {
  clearData: () => void;
};

const registeredRefs = new Map<number, RefObject<Mapbox.MapView>>();

const MapView: FunctionComponent<MapViewProps> & MapViewProperties = ({
  onRegionIsChanging,
  onRegionDidChange,
  enableFollowMode = false,
  children,
}) => {
  const mapViewRef = useRef<Mapbox.MapView>(null);
  const cameraRef = useRef<Mapbox.Camera>(null);

  const eventEmitter = useRef(new EventEmitter());

  const initialRegionHasBeenEmitted = useRef(false);

  const regionIsChangingEmitterCallback = useRef(
    debounce((region: Region) => eventEmitter.current.emit('regionIsChanging', region), 1, {
      maxWait: 16,
    })
  );
  const regionDidChangeEmitterCallback = useRef(
    debounce((region: Region) => eventEmitter.current.emit('regionDidChange', region), 100)
  );

  const [following, setFollowing] = useState(false);
  const [pitchEnabled, setPitchEnabled] = useState(false);

  const animatedRegion = useSharedValue<Region | null>(null);

  const handleRegionIsChanging = useCallback(
    (nextRegion: Region) => {
      if (onRegionIsChanging) {
        onRegionIsChanging(nextRegion);
      }
      animatedRegion.value = nextRegion;
      regionIsChangingEmitterCallback.current(nextRegion);
      regionDidChangeEmitterCallback.current(nextRegion);
      if (following && nextRegion.properties && nextRegion.properties.isUserInteraction) {
        setFollowing(false);
      }
    },
    [animatedRegion, following, onRegionIsChanging]
  );

  const handleRegionDidChange = useCallback(
    (nextRegion: Region) => {
      if (onRegionDidChange) {
        onRegionDidChange(nextRegion);
      }
      animatedRegion.value = nextRegion;
      if (!initialRegionHasBeenEmitted.current) {
        eventEmitter.current.emit('regionDidChange', nextRegion);
        initialRegionHasBeenEmitted.current = true;
      }
    },
    [animatedRegion, onRegionDidChange]
  );

  // Remove GeoJSON prefix since we imported Feature directly.
  const handleLongPress = (feature: Feature) => {
    eventEmitter.current.emit('longPressed', feature);
  };

  const lastKnownLocation = useRef<Mapbox.Location | null>(null);

  const goToLocation = useCallback(async (location: Mapbox.Location) => {
    if (mapViewRef.current == null || cameraRef.current == null) {
      throw new Error('Map has not been initialized');
    }
    const zoom = await mapViewRef.current.getZoom();
    cameraRef.current.setCamera({
      centerCoordinate: [location.coords.longitude, location.coords.latitude],
      zoomLevel: zoom,
      animationMode: 'flyTo',
      animationDuration: 250,
    });
  }, []);

  const handleUserLocationUpdate = useMemo(() => {
    if (!enableFollowMode) return undefined;
    return async (location: Mapbox.Location) => {
      lastKnownLocation.current = location;
      if (following) {
        await goToLocation(location);
      }
    };
  }, [enableFollowMode, following, goToLocation]);

  useEffect(() => {
    const id = Date.now();
    registeredRefs.set(id, mapViewRef);
    return () => {
      registeredRefs.delete(id);
    };
  }, []);

  return (
    <Mapbox.MapView
      style={{ flex: 1 }}
      ref={mapViewRef}
      logoEnabled={false}
      attributionEnabled={false}
      compassEnabled={false}
      rotateEnabled
      pitchEnabled={pitchEnabled}
      scaleBarEnabled={false}
      styleURL={Mapbox.StyleURL.Satellite}
      onRegionIsChanging={handleRegionIsChanging}
      onRegionDidChange={handleRegionDidChange}
      regionDidChangeDebounceTime={250}
      onLongPress={handleLongPress}
      projection={pitchEnabled ? 'globe' : 'mercator'}
    >
      <Mapbox.UserLocation
        visible={false}
        onUpdate={handleUserLocationUpdate}
        showsUserHeadingIndicator
      />
      <Mapbox.Atmosphere
        style={{
          color: 'rgb(186, 210, 235)',
          highColor: 'rgb(36, 92, 223)',
          horizonBlend: 0.02,
          spaceColor: 'rgb(11, 11, 25)',
          starIntensity: 0.6,
        }}
      />
      <Mapbox.SkyLayer
        id="sky-layer"
        style={{
          skyType: 'atmosphere',
          skyAtmosphereSun: [0.0, 0.0],
          skyAtmosphereSunIntensity: 15.0,
        }}
      />
      {children}
    </Mapbox.MapView>
  );
};

const clearData = () => registeredRefs.forEach((ref) => ref.current?.clearData());
MapView.clearData = clearData;

MapView.propTypes = {
  onRegionIsChanging: PropTypes.func,
  onRegionDidChange: PropTypes.func,
  enableFollowMode: PropTypes.bool,
  children: PropTypes.node,
};

export default MapView;

Observed behavior and steps to reproduce

When changing pitchEnabled, the mapbox layers all disappear. This started to happen after upgrading react-native version from 0.71.0 to 0.76.6

Expected behavior

We expect all mapbox layers to be still shown after toggle pitchEnabled.

Notes / preliminary analysis

I noticed that if I changed styleURL whenever the pitchEnabled changes, the layers are still loaded.
However, this is not an intended fix and would like to better understand how to make a fundamental fix to this bug.

Additional links and references

No response

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🪲 Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant