A React wrapper for Apple's MapKit JS, providing a seamless way to integrate Apple Maps into your React applications.
- 🗺️ Full TypeScript support
- 🔄 React component lifecycle integration
- đź“Ť Support for markers, custom annotations, and overlays
- 🎨 Customizable map styling and controls
- đź”’ Automatic token management and refresh
- 🎯 Built-in error handling
- đź’« Smooth animations and transitions
- 🖱️ Comprehensive event handling for map interactions, annotations, and user location
npm install @1amageek/mapkit
You'll need:
- An Apple Developer account
- A Maps ID from the Apple Developer portal
- A token generation endpoint in your backend
First, wrap your application with the MapKitProvider
:
import { MapKitProvider } from '@1amageek/mapkit';
const App = () => {
const fetchToken = async () => {
// Fetch your MapKit JS token from your server
const response = await fetch('your-token-endpoint');
const data = await response.json();
return {
token: data.token,
expiresAt: data.expiresAt // Unix timestamp in seconds
};
};
return (
<MapKitProvider
fetchToken={fetchToken}
options={{
language: 'en'
}}
>
<YourApp />
</MapKitProvider>
);
};
Then use the Map component:
import { Map, MarkerAnnotation } from '@1amageek/mapkit';
The library supports a wide range of events for the map, annotations, and user location:
The Map
component emits various events related to map display and interaction:
onRegionChangeStart
: Triggered when the map region starts changing.onRegionChangeEnd
: Triggered when the map region finishes changing.onRotationStart
: Triggered when the map starts rotating.onRotationEnd
: Triggered when the map finishes rotating.onScrollStart
: Triggered when the map starts scrolling.onScrollEnd
: Triggered when the map finishes scrolling.onZoomStart
: Triggered when the map starts zooming.onZoomEnd
: Triggered when the map finishes zooming.onMapTypeChange
: Triggered when the map type changes (e.g., from satellite to standard).onSingleTap
: Triggered when the map is tapped once.onDoubleTap
: Triggered when the map is double-tapped.onLongPress
: Triggered when the map is long-pressed.
onSelect
: Triggered when an annotation is selectedonDeselect
: Triggered when an annotation is deselectedonDrag
: Triggered while an annotation is being dragged, providing real-time updates.onDragStart
: Triggered when starting to drag an annotationonDragEnd
: Triggered when finishing dragging an annotation
onUserLocationChange
: Triggered when the user's location changes. Provides the new coordinate and timestamp.onUserLocationError
: Triggered when an error occurs while trying to retrieve the user's location.
Example usage with events:
import { Map, MarkerAnnotation } from '@1amageek/mapkit';
const MapComponent = () => {
return (
<Map
id="my-map"
options={{
showsUserLocation: true,
showsCompass: "Adaptive",
}}
onRegionChangeStart={(event: mapkit.EventBase<mapkit.Map>) => {
console.log("Region change start", event);
}}
onRegionChangeEnd={(event: mapkit.EventBase<mapkit.Map>) => {
console.log("Region change end", event);
}}
region={{
center: {
latitude: 35.6812,
longitude: 139.7671
},
span: {
latitudeDelta: 0.1,
longitudeDelta: 0.1
}
}}
>
<MarkerAnnotation
coordinate={{
latitude: 35.6812,
longitude: 139.7671
}}
title="Tokyo Tower"
subtitle="Tourist Attraction"
/>
</Map>
);
};
interface AnnotationEventHandlers {
onSelect?: (event: mapkit.EventBase<mapkit.Map>) => void;
onDeselect?: (event: mapkit.EventBase<mapkit.Map>) => void;
onDrag?: (event: mapkit.EventBase<mapkit.Map>) => void;
onDragStart?: (event: mapkit.EventBase<mapkit.Map>) => void;
onDragEnd?: (event: mapkit.EventBase<mapkit.Map>) => void;
}
<Map>
<MarkerAnnotation
coordinate={{
latitude: 35.6812,
longitude: 139.7671
}}
title="Tokyo Tower"
subtitle="Tourist Attraction"
draggable={true}
onSelect={(event) => {
// Event when user clicks/taps the annotation
const annotation = event.target;
console.log('Selected:', annotation.title);
console.log('At coordinate:', annotation.coordinate);
}}
onDrag={(event) => {
// Real-time coordinate updates during drag
console.log('Current position:', event.coordinate);
}}
onDragEnd={(event) => {
// Final location after drag ends
const { latitude, longitude } = event.coordinate;
console.log('Final position:', { latitude, longitude });
}}
/>
</Map>
<Map>
<CustomAnnotation
coordinate={{
latitude: 35.6812,
longitude: 139.7671
}}
draggable={true}
onSelect={(event) => {
// Access to DOM event and screen coordinates
console.log('Click position:', event.pointOnPage);
console.log('DOM event:', event.domEvent);
}}
callout={{
calloutContentForAnnotation: (annotation) => (
<div className="custom-callout">
<h3>{annotation.title}</h3>
<button onClick={() => handleCalloutAction(annotation)}>
Details
</button>
</div>
)
}}
>
<div className="custom-marker">
{/* Your custom marker content */}
</div>
</CustomAnnotation>
</Map>
const MapWithAnnotations = () => {
const handleAnnotationSelect = (event: mapkit.EventBase<mapkit.Map>) => {
// Common handler for all annotations
const { title, data } = event.target;
console.log('Selected location:', title);
console.log('Custom data:', data);
};
return (
<Map>
{locations.map((location) => (
<MarkerAnnotation
key={location.id}
coordinate={location.coordinate}
title={location.name}
data={location.customData}
onSelect={handleAnnotationSelect}
/>
))}
</Map>
);
};
<Map>
<ImageAnnotation
coordinate={{
latitude: 35.6812,
longitude: 139.7671
}}
url={{
1: "path/to/image.png",
2: "path/to/[email protected]",
3: "path/to/[email protected]"
}}
clusteringIdentifier="landmarks"
onSelect={(event) => {
if (event.target.memberAnnotations) {
// This is a cluster
console.log('Cluster size:', event.target.memberAnnotations.length);
} else {
// Single annotation
console.log('Selected image annotation');
}
}}
/>
</Map>
These event handlers are available for all annotation types (MarkerAnnotation
, ImageAnnotation
, CustomAnnotation
).
Create custom annotations with your own React components:
import { CustomAnnotation } from '@1amageek/mapkit';
const CustomPin = () => (
<CustomAnnotation
coordinate={{
latitude: 35.6812,
longitude: 139.7671
}}
>
<div className="custom-pin">
<img src="pin-icon.png" alt="Custom Pin" />
</div>
</CustomAnnotation>
);
Add various types of overlays to your map:
import { CircleOverlay, PolylineOverlay, PolygonOverlay } from '@1amageek/mapkit';
const MapWithOverlays = () => (
<Map>
<CircleOverlay
coordinate={{ latitude: 35.6812, longitude: 139.7671 }}
radius={1000}
options={{
strokeColor: "#FF0000",
lineWidth: 2,
fillColor: "#FF000033"
}}
/>
<PolylineOverlay
points={[
{ latitude: 35.6812, longitude: 139.7671 },
{ latitude: 35.6813, longitude: 139.7672 }
]}
options={{
strokeColor: "#0000FF",
lineWidth: 3
}}
/>
</Map>
);
The library provides built-in error handling:
const MapComponent = () => (
<Map
onMapError={(error) => {
console.error('Map error:', error);
}}
errorComponent={<div>Failed to load map</div>}
loadingComponent={<div>Loading map...</div>}
/>
);
Prop | Type | Required | Description |
---|---|---|---|
fetchToken | () => Promise | Yes | Function to fetch MapKit JS token |
options | MapKitInitOptions | No | Initialization options |
onError | (error: MapKitError) => void | No | Error handler |
Prop | Type | Required | Description |
---|---|---|---|
id | string | No | Map container ID |
options | mapkit.MapConstructorOptions | No | Map configuration options |
location | Location | No | Center location |
region | Region | No | Map region with center and span |
onMapError | (error: Error | MapKitError) => void | No | Error handler |
onAppear | (map: mapkit.Map) => void | No | Called when map is ready |
onChange | (map: mapkit.Map, newAnnotations: mapkit.Annotation[]) => void | No | Called when annotations/overlays change |
className | string | No | Additional CSS classes |
loadingComponent | ReactNode | No | Custom loading component |
errorComponent | ReactNode | No | Custom error component |
onRegionChangeStart | (event: mapkit.EventBase<mapkit.Map>) => void | No | Triggered when the map region starts changing. |
onRegionChangeEnd | (event: mapkit.EventBase<mapkit.Map>) => void | No | Triggered when the map region finishes changing. |
onRotationStart | (event: mapkit.EventBase<mapkit.Map>) => void | No | Triggered when the map starts rotating. |
onRotationEnd | (event: mapkit.EventBase<mapkit.Map>) => void | No | Triggered when the map finishes rotating. |
onScrollStart | (event: mapkit.EventBase<mapkit.Map>) => void | No | Triggered when the map starts scrolling. |
onScrollEnd | (event: mapkit.EventBase<mapkit.Map>) => void | No | Triggered when the map finishes scrolling. |
onZoomStart | (event: mapkit.EventBase<mapkit.Map>) => void | No | Triggered when the map starts zooming. |
onZoomEnd | (event: mapkit.EventBase<mapkit.Map>) => void | No | Triggered when the map finishes zooming. |
onMapTypeChange | (event: mapkit.EventBase<mapkit.Map>) => void | No | Triggered when the map type changes. |
onUserLocationChange | (event: mapkit.EventBase<mapkit.Map> & { coordinate: mapkit.Coordinate; timestamp: Date }) => void | No | Triggered when the user's location changes. |
onUserLocationError | (event: mapkit.EventBase<mapkit.Map> & { code: number; message: string }) => void | No | Triggered when an error occurs while trying to retrieve the user's location. |
onSingleTap | (event: mapkit.EventBase<mapkit.Map>) => void | No | Triggered when the map is tapped once. |
onDoubleTap | (event: mapkit.EventBase<mapkit.Map>) => void | No | Triggered when the map is double-tapped. |
onLongPress | (event: mapkit.EventBase<mapkit.Map>) => void | No | Triggered when the map is long-pressed. |
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.