Implement camera controls and expose MapHandle API in MapView

This commit is contained in:
2026-01-30 23:00:14 +05:30
parent 8710bc7a7f
commit 5b04cc9a58
4 changed files with 113 additions and 6 deletions

View File

@@ -1,11 +1,27 @@
import React from 'react'; import React, { useRef } from 'react';
import { SafeAreaView, StyleSheet } from 'react-native'; import { SafeAreaView, StyleSheet, Button, View } from 'react-native';
import { MapView, Marker } from '@lynkedup/map-sdk'; import { MapView, Marker } from '@lynkedup/map-sdk';
import type { MapHandle } from '@lynkedup/map-sdk';
export default function App() { export default function App() {
const mapRef = useRef<MapHandle | null>(null);
return ( return (
<SafeAreaView style={styles.container}> <SafeAreaView style={styles.container}>
<View style={styles.controls}>
<Button
title="Fly to SF"
onPress={() => mapRef.current?.flyTo({ latitude: 37.78825, longitude: -122.4324 })}
/>
<Button
title="Fit Bounds"
onPress={() =>
mapRef.current?.fitBounds({ latitude: 37.809, longitude: -122.410 }, { latitude: 37.779, longitude: -122.450 })
}
/>
</View>
<MapView <MapView
ref={mapRef}
style={styles.map} style={styles.map}
initialRegion={{ latitude: 37.78825, longitude: -122.4324, latitudeDelta: 0.0922, longitudeDelta: 0.0421 }} initialRegion={{ latitude: 37.78825, longitude: -122.4324, latitudeDelta: 0.0922, longitudeDelta: 0.0421 }}
> >
@@ -17,5 +33,6 @@ export default function App() {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1 }, container: { flex: 1 },
controls: { position: 'absolute', top: 40, left: 10, right: 10, zIndex: 1000, flexDirection: 'row', justifyContent: 'space-between' },
map: { flex: 1 } map: { flex: 1 }
}); });

View File

@@ -29,5 +29,26 @@ export default function App() {
} }
``` ```
## Camera Controls 🔧
This package exposes a small imperative `MapHandle` API via `ref` on `MapView`. Use it to animate the camera or fit bounds.
Example:
```ts
const mapRef = useRef<MapHandle | null>(null);
mapRef.current?.flyTo({ latitude: 37.78825, longitude: -122.4324 });
mapRef.current?.fitBounds({ latitude: 37.809, longitude: -122.410 }, { latitude: 37.779, longitude: -122.450 });
```
Available methods:
- `animateToRegion(region, duration?)` — animate to a region.
- `animateCamera(camera, options?)` — animate using camera properties.
- `flyTo(coordinate, duration?)` — quick helper that animates to a small region around `coordinate`.
- `fitBounds(northEast, southWest, options?)` — fit the two coordinates with optional `edgePadding`.
---
## Notes ## Notes
- This package delegates to `react-native-maps` for platform implementations. Follow `react-native-maps` docs for iOS/Android setup (Google Maps API key, pods, manifest permissions). - This package delegates to `react-native-maps` for platform implementations. Follow `react-native-maps` docs for iOS/Android setup (Google Maps API key, pods, manifest permissions).

View File

@@ -1,10 +1,78 @@
import React from 'react'; import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import RNMapView, { MapViewProps as RNMapViewProps } from 'react-native-maps'; import RNMapView, { MapViewProps as RNMapViewProps, Camera, Region, LatLng } from 'react-native-maps';
export type MapProps = RNMapViewProps; export type MapProps = RNMapViewProps;
const MapView: React.FC<MapProps> = (props) => { export type MapHandle = {
return <RNMapView {...props}>{props.children}</RNMapView>; animateToRegion: (region: Region, duration?: number) => void;
animateCamera: (camera: Partial<Camera>, options?: { duration?: number }) => void;
flyTo: (coordinate: LatLng, duration?: number) => void;
fitBounds: (
northEast: LatLng,
southWest: LatLng,
options?: { edgePadding?: { top: number; left: number; bottom: number; right: number }; animated?: boolean }
) => void;
getCamera?: () => Promise<Camera | undefined>;
}; };
const MapView = forwardRef<MapHandle, MapProps>((props, ref) => {
const mapRef = useRef<RNMapView | null>(null);
useImperativeHandle(
ref,
() => ({
animateToRegion(region: Region, duration = 500) {
if (mapRef.current?.animateToRegion) {
mapRef.current.animateToRegion(region, duration);
}
},
animateCamera(camera: Partial<Camera>, options) {
if (mapRef.current?.animateCamera) {
mapRef.current.animateCamera(camera, options);
}
},
flyTo(coordinate: LatLng, duration = 800) {
if (mapRef.current?.animateToRegion) {
const region: Region = {
latitude: coordinate.latitude,
longitude: coordinate.longitude,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
};
mapRef.current.animateToRegion(region, duration);
}
},
fitBounds(
northEast: LatLng,
southWest: LatLng,
options = { edgePadding: { top: 20, left: 20, bottom: 20, right: 20 }, animated: true }
) {
if (mapRef.current?.fitToCoordinates) {
mapRef.current.fitToCoordinates([northEast, southWest], {
edgePadding: options.edgePadding,
animated: options.animated,
});
}
},
async getCamera() {
if (mapRef.current?.getCamera) {
try {
return await mapRef.current.getCamera();
} catch {
return undefined;
}
}
return undefined;
},
}),
[]
);
return (
<RNMapView ref={mapRef} {...props}>
{props.children}
</RNMapView>
);
});
export default MapView; export default MapView;

View File

@@ -1,2 +1,3 @@
export { default as MapView } from './MapView'; export { default as MapView } from './MapView';
export type { MapHandle } from './MapView';
export { Marker, Polyline, PROVIDER_GOOGLE } from 'react-native-maps'; export { Marker, Polyline, PROVIDER_GOOGLE } from 'react-native-maps';