Всем привет, меня зовут Эдвард, и я Middle Front-end разработчик в команде Stellar 2H Group. Недавно я начал изучение разработки нативных view / модулей под React Native и хотел бы поделиться этим опытом, потому что мне пришлось столкнуться с некоторыми трудностями, о которых я позже поведаю.
В данной статье я буду использовать Webstorm и XCode. Если статья найдёт свой отклик, то попробуем реализовать то же самое, но под android. Приятного чтения!
Небольшой экскурс для тех, кто не в теме
React Native — это кроссплатформенный фреймворк с открытым исходным кодом для разработки нативных мобильных и настольных приложений на JavaScript и TypeScript, созданный Facebook Inc. (ныне Meta*)
Нативные модули для этой технологии пишутся на нативных языках хост-платформ (ios/android/windows/mac os). Например Objective C, Swift, Kotlin, C++.
В принципе, этой информации должно быть достаточно для минимального понимания, что здесь вообще происходит.
А что насчёт архитектуры?
На данный момент в RN реализовали новую архитектуру, которая называется Fabric, но её мы затрагивать не будем, поскольку в официальной документации сказано, что она экспериментальная и находится в активной разработке. источник
Создаём проект
Здесь всё просто. Запускаем вот эту команду, выбираем пункт native view, далее Kotlin & Swift и ждём, пока создастся темплейт проекта:
npx create-react-native-library@latest react-native-awesome-mapkit
Преднастройка проекта
-
Устанавливаем зависимости (
yarn/npm i/npm install) -
Добавляем зависимость в %название-вашей-либы%.podspec (4.3.1 — последняя версия на момент написания статьи)
s.dependency "YandexMapsMobile", "4.3.1-full" -
cd example -
npx pod-install
Готово! Мы можем писать нашу библиотеку
Шаг 0: Открываем проект
Открываем example/ios/AwesomeMapkitExample.xcworkspace в XCode
Шаг 1: Устанавливаем ключ Yandex Mapkit и язык карты
В example/ios/AwesomeMapkitExample/AppDelegate.mm прописываем следующие строчки:
#import <YandexMapsMobile/YMKMapKitFactory.h> ... - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.moduleName = @"AwesomeMapkitExample"; // You can add your custom initial props in the dictionary below. // They will be passed down to the ViewController used by React Native. self.initialProps = @{}; [YMKMapKit setApiKey:@"Ваш API ключ"]; // необязательное действие. По дефолту язык системы [YMKMapKit setLocale:@"ru_RU"]; [[YMKMapKit mapKit] onStart]; return [super application:application didFinishLaunchingWithOptions:launchOptions]; }
Отлично. Теперь при запуске приложения у нас будет проставляться API ключ Yandex карт. Замечу, что хардкод ключа и языка карты это временная мера. В следующих статьях мы сделаем возможность проставлять этот ключ и без доступа в натив (тот же самый expo-dev-client)
Шаг 2: Создаём нативный view карт
в корне находим папку ios и создаём папку MapView, а затем два файлика внутри: MapView.m и MapView.swift target выставляем Pods-AwesomeMapkitExample
При создании .swift файла XCode предложит создать bridging header. Не делаем этого, он уже есть, так как мы выбрали тип проекта Swift + Kotlin
Сначала напишем Swift часть нашего MapView:
import Foundation import React import YandexMapsMobile // объявляем структуру InitialCoords, которая реализует протокол Decodable struct InitialCoords: Decodable { var lat: Double; var lon: Double; var zoom: Float; var azimuth: Float; var tilt: Float; enum CodingKeys: String, CodingKey { case lat case lon case zoom case azimuth case tilt } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.lat = try container.decode(Double.self, forKey: .lat) self.lon = try container.decode(Double.self, forKey: .lon) self.zoom = try container.decode(Float.self, forKey: .zoom) self.azimuth = try container.decode(Float.self, forKey: .azimuth) self.tilt = try container.decode(Float.self, forKey: .tilt) } } // функция-проверка, запускается код в симуляторе или на реальном устройстве func isSimulator() -> Bool { #if targetEnvironment(simulator) return true #else return false #endif } // класс нативного view @objcMembers class MapView: UIView { // нативный View Yandex карты var ymkMapView: YMKMapView! // функция, которая принимает JS объект, передаваемый в пропс initialRegion func setInitialRegion(_ initialRegion: NSDictionary) { // декодируем объект в swift структуру let json = try! JSONSerialization.data(withJSONObject: initialRegion, options: []) let region: InitialCoords = try! JSONDecoder().decode(InitialCoords.self, from: json) // создаём точку, которая будет являться центром камеры let cameraPoint = YMKPoint(latitude: region.lat, longitude: region.lon) // создаём камеру let cameraPosition = YMKCameraPosition(target: cameraPoint, zoom: region.zoom, azimuth: region.azimuth, tilt: region.tilt) // передвигаем обзор карты на нужную точку без анимации ymkMapView.mapWindow.map.move(with: cameraPosition, animationType: YMKAnimation(type: YMKAnimationType.linear, duration: 0), cameraCallback: nil) } override init(frame: CGRect) { super.init(frame: frame) // инициализация карты if ymkMapView == nil { // включаем свойство vulkanPreferred, если карта запускается в симуляторе, // это нужно, чтобы не было лагов ymkMapView = YMKMapView(frame: CGRect.zero, vulkanPreferred: isSimulator()) clipsToBounds = true addSubview(ymkMapView) } } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } } // создаём класс менеджера нашего View @objc(MapViewManager) class MapViewManager: RCTViewManager { var mapView: MapView! override static func requiresMainQueueSetup() -> Bool { false } override func view() -> UIView! { mapView = MapView() return mapView } }
Те люди, которые уже давно разрабатывают нативные модули на Objective C могут спросить, зачем я передаю в Swift NSDictionary, а не преобразовываю его в структуру с помощью RCTConvert внутри Objective C? Ответ простой:
Итак, со Swift-частью мы разобрались, теперь осталось написать Objective C экспорты и пойти проверять данное дело в симуляторе:
// MapView.m #import <Foundation/Foundation.h> #import <React/RCTViewManager.h> #import <React/RCTBridgeModule.h> // экспортируем наш MapViewManager, реализация которого находится в MapView.swift @interface RCT_EXTERN_MODULE(MapViewManager, RCTViewManager) // экспортируем пропс initialRegion RCT_EXPORT_VIEW_PROPERTY(initialRegion, NSDictionary) @end
Немного корректировки JS стороны:
// src/index.tsx import { Platform, requireNativeComponent, UIManager, ViewStyle, } from 'react-native'; type MapViewProps = { style?: ViewStyle; initialRegion: { lat: number; lon: number; zoom: number; azimuth: number; tilt: number; }; }; const LINKING_ERROR = `The package 'react-native-awesome-mapkit' doesn't seem to be linked. Make sure: \n\n` + Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go\n'; const ComponentName = 'MapView'; export const MapView = UIManager.getViewManagerConfig(ComponentName) != null ? requireNativeComponent<MapViewProps>(ComponentName) : () => { throw new Error(LINKING_ERROR); };
// example/src/App.tsx import * as React from 'react'; import { MapView } from 'react-native-awesome-mapkit'; export default function App() { return ( <MapView style={{ flex: 1 }} initialRegion={{ lat: 55.751574, lon: 37.573856, zoom: 15, azimuth: 0, tilt: 0, }} /> ); }
Шаг 3: Радуемся жизни
Запускаем проект так:
yarn example start
yarn example ios
Если вы увидели примерно такую картину, то поздравляю, вы всё сделали правильно! Ура!
В следующей части я покажу, как контролировать children view, которые передаётся в нативный компонент, а это значит, что мы будем делать маркеры для нашей нативной карты)
Деятельность экстремистской организации (признана такой 21 марта 2022 года) Meta Platforms или Meta* запрещена в России. Компания владеет социальными сетями Facebook** и Instagram.
ссылка на оригинал статьи https://habr.com/ru/articles/736106/
Добавить комментарий