Форк библиотеки react-native-yamap, разработанной компанией Волга-Волга
Библиотека для интеграции MapKit SDK в React Native
| Version | React Native New Arch support |
|---|---|
| 6 | New Arch |
| 5 | Legacy + New Arch |
| 4 | Legacy Arch |
-
Компоненты
Circle,Marker,PolygonиPolyline:- дефолтное значение
handledизменено сtrueнаfalse
- дефолтное значение
-
Изменены названия и дефолтные значения props компонентов
YamapиClusteredYamap:interactive(true) →interactiveDisabled(false)scrollGesturesEnabled(true) →scrollGesturesDisabled(false)zoomGesturesEnabled(true) →zoomGesturesDisabled(false)tiltGesturesEnabled(true) →tiltGesturesDisabled(false)rotateGesturesEnabled(true) →rotateGesturesDisabled(false)fastTapEnabled(true) →fastTapDisabled(false)
yarn add react-native-yamap-plus
По умолчанию используется Full
Для изменения на Lite необходимо:
- android: в файле
android/build.gradleдобавить строку
buildscript {
ext {
...
+ useYandexMapsLite = true
}- ios: в начало
Podfileдобавить строку
+ ENV['USE_YANDEX_MAPS_LITE'] = "1"
...Для этого лучше всего зайти в корневой файл приложения, например App.js, и добавить инициализацию:
import {YamapInstance} from 'react-native-yamap-plus';
YamapInstance.init(API_KEY);import {YamapInstance} from 'react-native-yamap-plus';
const currentLocale = await YamapInstance.getLocale();
YamapInstance.setLocale('en_US'); // 'ru_RU' или другие
YamapInstance.resetLocale();-
getLocale(): Promise<string> - возвращает используемый язык карт;
-
setLocale(locale: string): Promise<void> - установить язык карт;
-
resetLocale(): Promise<void> - использовать для карт язык системы.
ВАЖНО!
- Для Android изменение языка карт вступит в силу только после перезапуска приложения.
- Для iOS методы изменения языка можно вызывать только до первого рендера карты. Также нельзя повторно вызывать метод, если язык уже изменялся (можно только после перезапуска приложения), иначе изменения приняты не будут, а в терминал будет выведено сообщение с предупреждением. В коде при этом не будет информации об ошибке.
import React from 'react';
import {Yamap} from 'react-native-yamap-plus';
const Map = () => {
return (
<Yamap
userLocationIcon={{ uri: 'https://www.clipartmax.com/png/middle/180-1801760_pin-png.png' }}
initialRegion={{
lat: 50,
lon: 50,
zoom: 10,
azimuth: 80,
tilt: 100
}}
style={{ flex: 1 }}
/>
);
};| Название | Тип | Стандартное значение | Описание |
|---|---|---|---|
| showUserPosition | boolean | false | Отслеживание геоданных и отображение позиции пользователя |
| followUser | boolean | false | слежение камеры за пользователем |
| userLocationIcon | ImageSource | false | Иконка для позиции пользователя. Доступны те же значения что и у компонента Image из React Native |
| userLocationIconScale | number | 1 | Масштабирование иконки пользователя |
| initialRegion | InitialRegion | Изначальное местоположение карты при загрузке | |
| interactiveDisabled | boolean | false | Интерактивная ли карта (перемещение по карте, отслеживание нажатий) |
| nightMode | boolean | false | Использование ночного режима |
| onMapLoaded | function | Колбек на загрузку карты | |
| onCameraPositionChange | function | Колбек на изменение положения камеры | |
| onCameraPositionChangeEnd | function | Колбек при завершении изменения положения камеры | |
| onMapPress | function | Событие нажития на карту. Возвращает координаты точки на которую нажали | |
| onMapLongPress | function | Событие долгого нажития на карту. Возвращает координаты точки на которую нажали | |
| userLocationAccuracyFillColor | string | Цвет фона зоны точности определения позиции пользователя | |
| userLocationAccuracyStrokeColor | string | Цвет границы зоны точности определения позиции пользователя | |
| userLocationAccuracyStrokeWidth | number | Толщина зоны точности определения позиции пользователя | |
| scrollGesturesDisabled | boolean | false | Включены ли жесты скролла |
| zoomGesturesDisabled | boolean | false | Включены ли жесты зума |
| tiltGesturesDisabled | boolean | false | Включены ли жесты наклона камеры двумя пальцами |
| rotateGesturesDisabled | boolean | false | Включены ли жесты поворота камеры |
| fastTapDisabled | boolean | false | Убрана ли задержка в 300мс при клике/тапе |
| logoPosition | YandexLogoPosition | {} | Позиция логотипа Яндекса на карте |
| logoPadding | YandexLogoPadding | {} | Отступ логотипа Яндекса на карте |
| mapType | string | 'vector' | Тип карты |
| mapStyle | string | {} | Стили карты согласно документации |
fitMarkers(points: Point[], duration?: number, animation?: Animation): void- подобрать положение камеры, чтобы вместить указанные маркеры (если возможно);fitAllMarkers(duration?: number, animation?: Animation): void- подобрать положение камеры, чтобы вместить все маркеры (если возможно);setCenter(center: { lon: number, lat: number }, zoom: number = 10, azimuth: number = 0, tilt: number = 0, duration: number = 0, animation: Animation = Animation.SMOOTH)- устанавливает камеру в точку с заданным zoom, поворотом по азимуту и наклоном карты (tilt). Можно параметризовать анимацию: длительность и тип. Если длительность установить 0, то переход будет без анимации. Возможные типы анимацийAnimation.SMOOTHиAnimation.LINEAR;setZoom(zoom: number, duration: number, animation: Animation)- изменить текущий zoom карты. Параметрыdurationиanimationработают по аналогии сsetCenter;getCameraPosition(callback: (position: CameraPosition) => void)- запрашивает положение камеры и вызывает переданный колбек с текущим значением;getVisibleRegion(callback: (region: VisibleRegion) => void)- запрашивает видимый регион и вызывает переданный колбек с текущим значением;setTrafficVisible(isVisible: boolean): void- включить/отключить отображение слоя с пробками на картах;getScreenPoints(point: Point[], callback: (result: {screenPoints: ScreenPoint[]}) => void)- получить кооординаты на экране (x и y) по координатам маркеров;getWorldPoints(screenPoint: ScreenPoint[], callback: (result: {worldPoints: Point[]}) => void)- получить координаты точек (lat и lon) по координатам на экране.
| Название | Тип | Стандартное значение | Описание |
|---|---|---|---|
| clusteredMarkers | Array<{point: Point, data: any}> | undefined | Массив точек на карте |
| сlusterIcon | ImageSource | undefined | Кастомная иконка для маркера (по умолчанию круг) |
| clusterColor | string | 'red' | Цвет фона метки-кластера |
| сlusterSize | YandexClusterSizes | {width: 32, height: 32} | Размер маркера |
| сlusterTextColor | string | 'black' | Цвет текста |
| сlusterTextSize | number | 45 | Размер текста |
| сlusterTextXOffset | number | 0 | Отступ для текста |
| сlusterTextYOffset | number | 0 | Отступ для текста |
ВАЖНО
- Компонент карт стилизуется, как и
Viewиз React Native. Если карта не отображается, после инициализации с валидным ключем API, вероятно необходимо прописать стиль, который опишет размеры компонента (height + widthилиflex); - При использовании изображений из JS (через
require('./img.png')) в дебаге и релизе на Android могут быть разные размеры маркера. Рекомендуется проверять рендер в релизной сборке.
import {Yamap, Marker} from 'react-native-yamap-plus';
<Yamap>
<Marker point={{ lat: 50, lon: 50 }} />
</Yamap>| Название | Тип | Описание |
|---|---|---|
| point | Point | Координаты точки для отображения маркера |
| scale | number | Масштабирование иконки маркера. Не работает если использовать children у маркера |
| source | ImageSource | Данные для изображения маркера. |
| children | ReactElement | Рендер маркера как компонента. DEPRECATED. Используйте source. |
| onPress | function | Действие при нажатии/клике |
| anchor | { x: number, y: number } | Якорь иконки маркера. Координаты принимают значения от 0 до 1 |
| zIndex | number | zIndex для объекта на карте |
| visible | boolean | Отображение маркера на карте |
| handled | boolean | Включение(false)/отключение(true) всплытия события нажатия для родителя default:false |
animatedMoveTo(point: Point, duration: number)- плавное изменение позиции маркера;animatedRotateTo(angle: number, duration: number)- плавное вращение маркера.
Существует два способа отобразить произвольный компонент в маркере:
- Передать компонент в children
- Выполнить snapshot компонента и полученное изображение передать в source
Простой, но нежелательный способ. Помечен как deprecated.
Поддержка children была добавлена для совместимости с API react-native-maps. Комментарий автора оригинальной библиотеки.
При использовании children библиотека автоматически создаёт snapshot view-компонента на нативном уровне и использует его для отображения маркера. Особенности:
-
Некорректный внешний вид: Из-за особенностей нативного snapshot вёрстка компонента внутри маркера может отличаться от его обычного рендера вне карты.
-
Нестабильное обновление: При обновлении состояния компонента не гарантируется перерисовка содержимого маркера, особенно при изменении состояния вложенных элементов. Поведение на iOS и Android может отличаться.
Workarounds для принудительного обновления:
- Изменение
keyу первого дочернего элемента внутриchildrenвызовет пересоздание снимка. - Изменение
keyу самогоMarkerпересоздаст снимок, но вызовет заметное мерцание: маркер исчезнет и появится заново.
-
Производительность: Для каждого маркера создаётся уникальный снимок, даже если маркеры визуально одинаковы. При большом количестве маркеров это приводит к просадкам производительности и расходу памяти.
-
Неконтролируемый момент снимка: Снимок создаётся библиотекой автоматически, и асинхронный контент (например, изображения по URL) может не успеть загрузиться.
Надёжнее самостоятельно создать снимок компонента и передать его в source. Рекомендуется использовать react-native-view-shot. Это работает стабильно на обеих платформах и позволяет переиспользовать один снимок для множества маркеров.
Используйте collapsable={false} для View, с которого делается snapshot - это предотвратит возможные проблемы на Android.
Примеры кода
Простой компонент, доступный для рендера сразу (синхронный):
const MyMarker = ({ point }) => {
const viewRef = useRef<View>(null);
const [source, setSource] = useState();
const handleLayout = useCallback(async () => {
if (!viewRef.current) return;
const base64 = await captureRef(viewRef, {
format: 'png',
quality: 1,
result: 'base64',
});
setSource({ uri: `data:image/png;base64,${base64}` });
}, []);
return (
<>
{!source && (
<View style={{ position: 'absolute' }} ref={viewRef} collapsable={false} onLayout={handleLayout}>
<AnySyncLoadedComponent />
</View>
)}
{source && <Marker point={point} source={source} />}
</>
);
};
Компонент с ожиданием контента (асинхронная загрузка):
const MyMarkerWithImage = ({ point, imageUrl }) => {
const viewRef = useRef<View>(null);
const [source, setSource] = useState();
const handleCapture = useCallback(async () => {
if (!viewRef.current) return;
const base64 = await captureRef(viewRef, {
format: 'png',
quality: 1,
result: 'base64',
});
setSource({ uri: `data:image/png;base64,${base64}` });
}, []);
return (
<>
{!source && (
<View style={{ position: 'absolute' }} collapsable={false}>
<Image
source={{ uri: imageUrl }}
onLoad={handleCapture}
/>
</View>
)}
{source && <Marker point={point} source={source} />}
</>
);
};
import {Yamap, Circle} from 'react-native-yamap-plus';
<Yamap>
<Circle center={{ lat: 50, lon: 50 }} radius={300} />
</Yamap>| Название | Тип | Описание |
|---|---|---|
| center | Point | Координаты центра круга |
| radius | number | Радиус круга в метрах |
| fillColor | string | Цвет заливки |
| strokeColor | string | Цвет границы |
| strokeWidth | number | Толщина границы |
| onPress | function | Действие при нажатии/клике |
| zIndex | number | zIndex для объекта на карте |
| handled | boolean | Включение(false)/отключение(true) всплытия события нажатия для родителя default:false |
import {Yamap, Polyline} from 'react-native-yamap-plus';
<Yamap>
<Polyline
points={[
{ lat: 50, lon: 50 },
{ lat: 50, lon: 20 },
{ lat: 20, lon: 20 },
]}
/>
</Yamap>| Название | Тип | Описание |
|---|---|---|
| points | Point[] | Массив точек линии |
| strokeColor | string | Цвет линии |
| strokeWidth | number | Толщина линии |
| outlineColor | string | Цвет обводки |
| outlineWidth | number | Толщина обводки |
| dashLength | number | Длина штриха |
| dashOffset | number | Отступ первого штриха от начала полилинии |
| gapLength | number | Длина разрыва между штрихами |
| onPress | function | Действие при нажатии/клике |
| zIndex | number | zIndex для объекта на карте |
| handled | boolean | Включение(false)/отключение(true) всплытия события нажатия для родителя default:false |
import {Yamap, Polygon} from 'react-native-yamap-plus';
<Yamap>
<Polygon
points={[
{ lat: 50, lon: 50 },
{ lat: 50, lon: 20 },
{ lat: 20, lon: 20 },
]}
/>
</Yamap>| Название | Тип | Описание |
|---|---|---|
| points | Point[] | Массив точек линии |
| fillColor | string | Цвет заливки |
| strokeColor | string | Цвет границы |
| strokeWidth | number | Толщина границы |
| innerRings | (Point[])[] | Массив полилиний, которые образуют отверстия в полигоне |
| onPress | function | Действие при нажатии/клике |
| zIndex | number | zIndex для объекта на карте |
| handled | boolean | Включение(false)/отключение(true) всплытия события нажатия для родителя default:false |
import {Transport} from 'react-native-yamap-plus';
const routes = await Transport.findRoutes(points, vehicles) // универсальный поиск маршрутов
const masstransitRoutes = await findMasstransitRoutes(points) // маршрутов на общественном транспорте
const pedestrianRoutes = await findPedestrianRoutes(points) // пешеходные маршруты
const drivingRoutes = await findDrivingRoutes(points) // маршруты на автомобилеТип роутера зависит от переданного в функцию массива vehicles:
- Если передан пустой массив (
findRoutes(points, [])), то будет использованPedestrianRouter; - Если передан массив с одним элементом
'car'(findRoutes(points, ['car'])), то будет использованDrivingRouter; - Во всех остальных случаях используется
MasstransitRouter.
Для поиска с геоподсказками нужно воспользоваться модулем Suggest:
import {Suggest} from 'react-native-yamap-plus';
const find = async (query: string, options?: SuggestOptions) => {
const suggestions = await Suggest.suggest(query, options);
// suggestion = [{
// subtitle: "Москва, Россия"
// title: "улица Льва Толстого, 16"
// uri: "ymapsbm1://geo?ll=37.587093%2C55.733974&spn=0.001000%2C0.001000&text=%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D1%8F%2C%20%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0%2C%20%D1%83%D0%BB%D0%B8%D1%86%D0%B0%20%D0%9B%D1%8C%D0%B2%D0%B0%20%D0%A2%D0%BE%D0%BB%D1%81%D1%82%D0%BE%D0%B3%D0%BE%2C%2016"
// }, ...]
const suggestionsWithCoards = await Suggest.suggestWithCoords(query, options);
// suggestionsWithCoards = [{
// subtitle: "Москва, Россия"
// title: "улица Льва Толстого, 16"
// lat: 55.733974
// lon: 37.587093
// uri: "ymapsbm1://geo?ll=37.587093%2C55.733974&spn=0.001000%2C0.001000&text=%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D1%8F%2C%20%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0%2C%20%D1%83%D0%BB%D0%B8%D1%86%D0%B0%20%D0%9B%D1%8C%D0%B2%D0%B0%20%D0%A2%D0%BE%D0%BB%D1%81%D1%82%D0%BE%D0%B3%D0%BE%2C%2016"
// }, ...]
// After searh session is finished
Suggest.reset();
}Для поиска нужно воспользоваться модулем Suggest:
import {Search} from 'react-native-yamap-plus';
const find = async (query: string, options?: SuggestOptions) => {
// можно использовать Point, BoundingBox, Polyline и Polygon (4 точки, без innerRings)
const search = await Search.searchText(
'Москва',
{ point: { lat: 54, lon: 53 } },
{ disableSpellingCorrection: true, geometry: true },
);
// второй параметр это зум, определяющий на сколько малые объекты искать
const searchByPoint = await Search.searchPoint({ lat: 54, lon: 53 }, 10, {
disableSpellingCorrection: true,
geometry: true,
});
const resolveURI = await Search.resolveURI("ymapsbm1://geo?data=IgoNAQBYQhUBAFhC", {
disableSpellingCorrection: true,
geometry: true,
});
const searchByURI = await Search.searchByURI("ymapsbm1://geo?data=IgoNAQBYQhUBAFhC", {
disableSpellingCorrection: true,
geometry: true,
});
// {"Components": [{"kind": "4", "name": "Малиновский сельсовет"}, {"kind": "4", "name": "Белебеевский район"}, {"kind": "3", "name": "Республика Башкортостан"}, {"kind": "19", "name": "Понтийско-Каспийская степь"}, {"kind": "19", "name": "Понтийско-Каспийская степь"}, {"kind": "19", "name": "Понтийско-Каспийская степь"}, {"kind": "3", "name": "Приволжский федеральный округ"}, {"kind": "1", "name": "Россия"}], "country_code": "RU", "formatted": "Россия, Республика Башкортостан, Белебеевский район, Малиновский сельсовет", "uri": "ymapsbm1://geo?data=IgoNAQBYQhUBAFhC"}
}Также теперь можно воспользоваться геокодированием из поиска
import {Search} from 'react-native-yamap-plus';
const address = Search.geocodePoint({lat: 54, lon: 53});
// {"Components": [{"kind": "4", "name": "Малиновский сельсовет"}, {"kind": "4", "name": "Белебеевский район"}, {"kind": "3", "name": "Республика Башкортостан"}, {"kind": "19", "name": "Понтийско-Каспийская степь"}, {"kind": "19", "name": "Понтийско-Каспийская степь"}, {"kind": "19", "name": "Понтийско-Каспийская степь"}, {"kind": "3", "name": "Приволжский федеральный округ"}, {"kind": "1", "name": "Россия"}], "country_code": "RU", "formatted": "Россия, Республика Башкортостан, Белебеевский район, Малиновский сельсовет", "uri": "ymapsbm1://geo?data=IgoNAQBYQhUBAFhC"}
const point = Search.geocodeAddress(address.formatted);
// возвращает координаты по адресу {"lat": 53.999187242158015, "lon": 54.089440735780194}import React from 'react';
import {ClusteredYamap} from 'react-native-yamap-plus';
const Map = () => {
return (
<ClusteredYamap
clusterColor="red"
clusteredMarkers={[
{
point: {
lat: 56.754215,
lon: 38.622504,
},
data: {},
},
{
point: {
lat: 56.754215,
lon: 38.222504,
},
data: {},
},
]}
renderMarker={(info, index) => (
<Marker
key={index}
point={info.point}
/>
)}
style={{flex: 1}}
/>
);
};Для подключения нативного модуля в приложение с expo используйте expo prebuild. Он выполнит eject и сгенерирует привычные папки android и ios с нативным кодом. Это позволит использовать любую библиотеку так же, как и приложение с react native cli.
- В никоторых случаях в симуляторе ios может крашиться приложение с ошибкой
failed assertion `The following Metal object is being destroyed while still required to be alive by the command buffer
Решение: In Xcode, in the "Product" Menu, go to "Scheme", then "Edit Scheme...". In the left panel select "Run (Debug)" and in Diagnostic tab uncheck "API Validation" under Metal settings.