리액트 네이티브 네이버지도 연동 방법 입니다.
개발환경 : Android, react-native cli (expo 아님)
구글링을 열심히 해본 결과
크게 2군데 react-naver map 브릿지를 제공하고 있었습니다.
- zeakd.github.io/react-naver-maps/#/React%20Naver%20Maps?id=introduction
장점, Guide Doc 제공.
단점, 지금 app run 안됨... err 발생.
- github.com/QuadFlask/react-native-naver-map
장점, 아주 잘 동작함
단점, doc 이 없음
잘 동작하는 녀석으로 골라 사용해보겠습니다.
우선, naver map service를 등록하고
api key를 발급받아야합니다.
이게 조금 귀찮은게 naver id가 있어도 따로 회원가입을 해야 하네요.
이용 신청하기 - Application 등록 - map 모두 추가
그리고, 본인 app의 package name 을 꼭 넣어야 합니다.
넣지않으면 map이 안보이는 불상사가 발생합니다.
이걸로 몇시간 삽질했네요.
아직 모른다면, project init 후 설정하세요.
그리고, 인증 정보의 Client ID 를 따로 기록해 둡니다.
이제 사전 준비가 끝났으니 app project 를 생성합니다.
react-native init naverMapsInsider
cd naverMapsInsider
npm install react-native-nmap --save
project android folder 안에서 다음과 같이 진행합니다.
AndroidManifest.xml
<manifest>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<application>
<meta-data android:name="com.naver.maps.map.CLIENT_ID"
android:value="YOUR_CLIENT_ID_HERE" />
</application>
</manifest>
build.gradle
allprojects {
repositories {
google()
jcenter()
maven { url 'https://navercorp.bintray.com/maps' }
}
}
혹시, android sdk 를 못찾는다는 compile error 발생한다면 아래 추가
android root folder 에
local.properties 파일 생성 후 아래 입력.
sdk.dir=C\:\\Users\\xxxxx\\AppData\\Local\\Android\\Sdk
그리고 app 에서 사용할 component 들을 install 합니다.
stack , bottom tab 등의 react-navigation component 들을 사용해볼 예정입니다.
npm install --save react-navigation
npm install @react-navigation/stack
npm install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
npm install @react-navigation/bottom-tabs
npm install @react-navigation/native
이제 모든 준비가 끝났고, app을 실행 합니다.
react-native run-android
이제 map을 띄우기 위해 code 수정
App.js
import 'react-native-gesture-handler';
import React, {useEffect} from 'react';
import NaverMapView, {Circle, Marker, Path, Polyline, Polygon, Align} from "./map";
import {PermissionsAndroid, Platform, Text, TouchableOpacity, View} from "react-native";
import {NavigationContainer} from '@react-navigation/native';
import {createBottomTabNavigator} from "@react-navigation/bottom-tabs";
import {createStackNavigator} from "@react-navigation/stack";
const P0 = {latitude: 37.564362, longitude: 126.977011};
const P1 = {latitude: 37.565051, longitude: 126.978567};
const P2 = {latitude: 37.565383, longitude: 126.976292};
const P4 = {latitude: 37.564834, longitude: 126.977218};
const Tab = createBottomTabNavigator();
const Stack = createStackNavigator();
const App = () => {
return <NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="home" component={HomeScreen}/>
<Stack.Screen name="stack" component={MapViewScreen}/>
</Stack.Navigator>
</NavigationContainer>
}
const HomeScreen = () =>
<Tab.Navigator>
<Tab.Screen name={"map"} component={MapViewScreen}/>
<Tab.Screen name={"text"} component={TextScreen}/>
</Tab.Navigator>
const TextScreen = () => {
return <Text>text</Text>
}
const MapViewScreen = ({navigation}) => {
useEffect(() => {
requestLocationPermission();
}, []);
return <>
<NaverMapView style={{width: '100%', height: '100%'}}
showsMyLocationButton={true}
center={{...P0, zoom: 16}}
onTouch={e => console.warn('onTouch', JSON.stringify(e.nativeEvent))}
onCameraChange={e => console.warn('onCameraChange', JSON.stringify(e))}
onMapClick={e => console.warn('onMapClick', JSON.stringify(e))}
useTextureView>
<Marker coordinate={P0} onClick={() => console.warn('onClick! p0')} caption={{text: "test caption", align: Align.Left}}/>
<Marker coordinate={P1} pinColor="blue" onClick={() => console.warn('onClick! p1')}/>
<Marker coordinate={P2} pinColor="red" onClick={() => console.warn('onClick! p2')}/>
<Marker coordinate={P4} onClick={() => console.warn('onClick! p4')} image={require("./marker.png")} width={48} height={48}/>
<Path coordinates={[P0, P1]} onClick={() => console.warn('onClick! path')} width={10}/>
<Polyline coordinates={[P1, P2]} onClick={() => console.warn('onClick! polyline')}/>
<Circle coordinate={P0} color={"rgba(255,0,0,0.3)"} radius={200} onClick={() => console.warn('onClick! circle')}/>
<Polygon coordinates={[P0, P1, P2]} color={`rgba(0, 0, 0, 0.5)`} onClick={() => console.warn('onClick! polygon')}/>
</NaverMapView>
<TouchableOpacity style={{position: 'absolute', bottom: '10%', right: 8}} onPress={() => navigation.navigate('stack')}>
<View style={{backgroundColor: 'gray', padding: 4}}>
<Text style={{color: 'white'}}>open stack</Text>
</View>
</TouchableOpacity>
<Text style={{position: 'absolute', top: '95%', width: '100%', textAlign: 'center'}}>Icon made by Pixel perfect from www.flaticon.com</Text>
</>
};
async function requestLocationPermission() {
if (Platform.OS !== 'android') return;
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
title: 'Location Permission',
message: 'show my location need Location permission',
buttonNeutral: 'Ask Me Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
},
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
console.log('You can use the location');
} else {
console.log('Location permission denied');
}
} catch (err) {
console.warn(err);
}
}
export default App;
~/map/index.js
import React, { Component } from 'react';
import { requireNativeComponent, findNodeHandle, UIManager, Platform, NativeModules, Image, } from 'react-native';
const RNNaverMapView = requireNativeComponent('RNNaverMapView');
const RNNaverMapViewTexture = Platform.select({
android: () => requireNativeComponent('RNNaverMapViewTexture'),
ios: () => RNNaverMapView
})();
const RNNaverMapMarker = requireNativeComponent('RNNaverMapMarker');
const RNNaverMapPathOverlay = requireNativeComponent('RNNaverMapPathOverlay');
const RNNaverMapPolylineOverlay = requireNativeComponent('RNNaverMapPolylineOverlay');
const RNNaverMapCircleOverlay = requireNativeComponent('RNNaverMapCircleOverlay');
const RNNaverMapPolygonOverlay = requireNativeComponent('RNNaverMapPolygonOverlay');
export var TrackingMode;
(function (TrackingMode) {
TrackingMode[TrackingMode["None"] = 0] = "None";
TrackingMode[TrackingMode["NoFollow"] = 1] = "NoFollow";
TrackingMode[TrackingMode["Follow"] = 2] = "Follow";
TrackingMode[TrackingMode["Face"] = 3] = "Face";
})(TrackingMode || (TrackingMode = {}));
export var MapType;
(function (MapType) {
MapType[MapType["Basic"] = 0] = "Basic";
MapType[MapType["Navi"] = 1] = "Navi";
MapType[MapType["Satellite"] = 2] = "Satellite";
MapType[MapType["Hybrid"] = 3] = "Hybrid";
MapType[MapType["Terrain"] = 4] = "Terrain";
})(MapType || (MapType = {}));
export var LayerGroup;
(function (LayerGroup) {
LayerGroup["LAYER_GROUP_BUILDING"] = "building";
LayerGroup["LAYER_GROUP_TRANSIT"] = "transit";
LayerGroup["LAYER_GROUP_BICYCLE"] = "bike";
LayerGroup["LAYER_GROUP_TRAFFIC"] = "ctt";
LayerGroup["LAYER_GROUP_CADASTRAL"] = "landparcel";
LayerGroup["LAYER_GROUP_MOUNTAIN"] = "mountain";
})(LayerGroup || (LayerGroup = {}));
export var Gravity;
(function (Gravity) {
Gravity[Gravity["NO_GRAVITY"] = 0] = "NO_GRAVITY";
Gravity[Gravity["AXIS_SPECIFIED"] = 1] = "AXIS_SPECIFIED";
Gravity[Gravity["AXIS_PULL_BEFORE"] = 2] = "AXIS_PULL_BEFORE";
Gravity[Gravity["AXIS_PULL_AFTER"] = 4] = "AXIS_PULL_AFTER";
Gravity[Gravity["AXIS_X_SHIFT"] = 0] = "AXIS_X_SHIFT";
Gravity[Gravity["AXIS_Y_SHIFT"] = 4] = "AXIS_Y_SHIFT";
Gravity[Gravity["TOP"] = 48] = "TOP";
Gravity[Gravity["BOTTOM"] = 80] = "BOTTOM";
Gravity[Gravity["LEFT"] = 3] = "LEFT";
Gravity[Gravity["RIGHT"] = 5] = "RIGHT";
Gravity[Gravity["CENTER_VERTICAL"] = 16] = "CENTER_VERTICAL";
Gravity[Gravity["CENTER_HORIZONTAL"] = 1] = "CENTER_HORIZONTAL";
})(Gravity || (Gravity = {}));
export var Align;
(function (Align) {
Align[Align["Center"] = 0] = "Center";
Align[Align["Left"] = 1] = "Left";
Align[Align["Right"] = 2] = "Right";
Align[Align["Top"] = 3] = "Top";
Align[Align["Bottom"] = 4] = "Bottom";
Align[Align["TopLeft"] = 5] = "TopLeft";
Align[Align["TopRight"] = 6] = "TopRight";
Align[Align["BottomRight"] = 7] = "BottomRight";
Align[Align["BottomLeft"] = 8] = "BottomLeft";
})(Align || (Align = {}));
export default class NaverMapView extends Component {
constructor() {
super(...arguments);
this.resolveRef = (ref) => {
this.ref = ref;
this.nodeHandle = findNodeHandle(ref);
};
this.animateToCoordinate = (coord) => {
this.dispatchViewManagerCommand('animateToCoordinate', [coord]);
};
this.animateToTwoCoordinates = (c1, c2) => {
this.dispatchViewManagerCommand('animateToTwoCoordinates', [c1, c2]);
};
this.animateToCoordinates = (coords, bounds) => {
this.dispatchViewManagerCommand("animateToCoordinates", [coords, bounds]);
};
this.animateToRegion = (region) => {
this.dispatchViewManagerCommand('animateToRegion', [region]);
};
this.setLocationTrackingMode = (mode) => {
this.dispatchViewManagerCommand('setLocationTrackingMode', [mode]);
};
this.showsMyLocationButton = (show) => {
this.dispatchViewManagerCommand('showsMyLocationButton', [show]);
};
this.dispatchViewManagerCommand = (command, arg) => {
return Platform.select({
// @ts-ignore
android: () => UIManager.dispatchViewManagerCommand(this.nodeHandle,
// @ts-ignore
UIManager.getViewManagerConfig('RNNaverMapView').Commands[command], arg),
ios: () => NativeModules[`RNNaverMapView`][command](this.nodeHandle, ...arg),
})();
};
this.handleOnCameraChange = (event) => this.props.onCameraChange && this.props.onCameraChange(event.nativeEvent);
this.handleOnMapClick = (event) => this.props.onMapClick && this.props.onMapClick(event.nativeEvent);
}
render() {
const { onInitialized, center, tilt, bearing, mapPadding, logoMargin, nightMode, useTextureView, } = this.props;
const ViewClass = useTextureView ? RNNaverMapViewTexture : RNNaverMapView;
return React.createElement(ViewClass, Object.assign({ ref: this.resolveRef }, this.props, { onInitialized: onInitialized, center: center, mapPadding: mapPadding, logoMargin: logoMargin, tilt: tilt, bearing: bearing, nightMode: nightMode, onCameraChange: this.handleOnCameraChange, onMapClick: this.handleOnMapClick }));
}
}
export class Marker extends Component {
render() {
return React.createElement(RNNaverMapMarker, Object.assign({}, this.props, { image: getImageUri(this.props.image) }));
}
}
export class Circle extends Component {
render() {
return React.createElement(RNNaverMapCircleOverlay, Object.assign({}, this.props));
}
}
export class Polyline extends Component {
render() {
return React.createElement(RNNaverMapPolylineOverlay, Object.assign({}, this.props));
}
}
export class Polygon extends Component {
render() {
return React.createElement(RNNaverMapPolygonOverlay, Object.assign({}, this.props));
}
}
export class Path extends Component {
render() {
return React.createElement(RNNaverMapPathOverlay, Object.assign({}, this.props, { pattern: getImageUri(this.props.pattern) }));
}
}
function getImageUri(src) {
let imageUri = null;
if (src) {
let image = Image.resolveAssetSource(src) || { uri: null };
imageUri = image.uri;
}
return imageUri;
}
~/map/index.d.ts
import React, { Component } from 'react';
import { StyleProp, ViewStyle, ImageSourcePropType } from 'react-native';
declare const RNNaverMapView: any;
export interface Coord {
latitude: number;
longitude: number;
}
export interface Region extends Coord {
latitudeDelta: number;
longitudeDelta: number;
}
export declare enum TrackingMode {
None = 0,
NoFollow = 1,
Follow = 2,
Face = 3
}
export declare enum MapType {
Basic = 0,
Navi = 1,
Satellite = 2,
Hybrid = 3,
Terrain = 4
}
export declare enum LayerGroup {
LAYER_GROUP_BUILDING = "building",
LAYER_GROUP_TRANSIT = "transit",
LAYER_GROUP_BICYCLE = "bike",
LAYER_GROUP_TRAFFIC = "ctt",
LAYER_GROUP_CADASTRAL = "landparcel",
LAYER_GROUP_MOUNTAIN = "mountain"
}
export declare enum Gravity {
NO_GRAVITY = 0,
AXIS_SPECIFIED = 1,
AXIS_PULL_BEFORE = 2,
AXIS_PULL_AFTER = 4,
AXIS_X_SHIFT = 0,
AXIS_Y_SHIFT = 4,
TOP = 48,
BOTTOM = 80,
LEFT = 3,
RIGHT = 5,
CENTER_VERTICAL = 16,
CENTER_HORIZONTAL = 1
}
export declare enum Align {
Center = 0,
Left = 1,
Right = 2,
Top = 3,
Bottom = 4,
TopLeft = 5,
TopRight = 6,
BottomRight = 7,
BottomLeft = 8
}
export interface Rect {
left?: number;
top?: number;
right?: number;
bottom?: number;
}
export interface NaverMapViewProps {
style?: StyleProp<ViewStyle>;
center?: Coord & {
zoom?: number;
tilt?: number;
bearing?: number;
};
tilt?: number;
bearing?: number;
mapPadding?: Rect;
logoMargin?: Rect;
logoGravity?: Gravity;
onInitialized?: Function;
onCameraChange?: (event: {
latitude: number;
longitude: number;
zoom: number;
}) => void;
onMapClick?: (event: {
x: number;
y: number;
latitude: number;
longitude: number;
}) => void;
onTouch?: () => void;
showsMyLocationButton?: boolean;
compass?: boolean;
scaleBar?: boolean;
zoomControl?: boolean;
mapType?: MapType;
buildingHeight?: number;
nightMode?: boolean;
useTextureView?: boolean;
}
export default class NaverMapView extends Component<NaverMapViewProps> {
ref?: RNNaverMapView;
nodeHandle?: null | number;
private resolveRef;
animateToCoordinate: (coord: Coord) => void;
animateToTwoCoordinates: (c1: Coord, c2: Coord) => void;
animateToCoordinates: (coords: Coord[], bounds?: {
top: number;
bottom: number;
left: number;
right: number;
}) => void;
animateToRegion: (region: Region) => void;
setLocationTrackingMode: (mode: number) => void;
showsMyLocationButton: (show: boolean) => void;
private dispatchViewManagerCommand;
handleOnCameraChange: (event: React.SyntheticEvent<{}, {
latitude: number;
longitude: number;
zoom: number;
}>) => void;
handleOnMapClick: (event: React.SyntheticEvent<{}, {
x: number;
y: number;
latitude: number;
longitude: number;
}>) => void;
render(): JSX.Element;
}
interface RNNaverMapView extends React.Component<{}, any> {
}
export interface MapOverlay {
coordinate: Coord;
onClick?: () => void;
}
export interface MarkerProps extends MapOverlay {
anchor?: {
x: number;
y: number;
};
pinColor?: string;
rotation?: number;
flat?: boolean;
image?: ImageSourcePropType;
width?: number;
height?: number;
alpha?: number;
animated?: boolean;
caption?: {
text?: string;
align?: Align;
textSize?: number;
color?: string;
haloColor?: string;
};
subCaption?: {
text?: string;
textSize?: number;
color?: number;
haloColor?: number;
};
}
export declare class Marker extends Component<MarkerProps> {
render(): JSX.Element;
}
export interface CircleProps extends MapOverlay {
radius?: number;
color?: string;
outlineWidth?: number;
outlineColor?: string;
zIndex?: number;
}
export declare class Circle extends Component<CircleProps> {
render(): JSX.Element;
}
interface PolylineProps extends Omit<MapOverlay, "coordinate"> {
coordinates: Coord[];
strokeWidth?: number;
strokeColor?: string;
}
export declare class Polyline extends Component<PolylineProps> {
render(): JSX.Element;
}
interface PolygonProps extends Omit<MapOverlay, "coordinate"> {
coordinates: Coord[];
outlineWidth?: number;
outlineColor?: string;
color?: string;
holes?: Coord[][];
}
export declare class Polygon extends Component<PolygonProps> {
render(): JSX.Element;
}
export interface PathProps extends Omit<MapOverlay, "coordinate"> {
coordinates: Coord[];
width?: number;
color?: string;
outlineWidth?: number;
passedColor?: string;
outlineColor?: string;
passedOutlineColor?: string;
pattern?: ImageSourcePropType;
patternInterval?: number;
progress?: number;
zIndex?: number;
}
export declare class Path extends Component<PathProps> {
render(): JSX.Element;
}
export {};
세군데 수정해주면, 다음과 같이 동작 합니다.
'Mobile 개발 > RN Project - Map Chat' 카테고리의 다른 글
RN - 클릭 위치 주소 얻기, reverse geocoding 적용 (2) | 2021.01.01 |
---|---|
RN - 채팅, GiftedChat with realtime database (2) | 2021.01.01 |
RN - Project 에서 활용한 라이브러리 (2) | 2020.12.05 |
React Native map-based chat app with firebase (0) | 2020.09.16 |
RN (React Native) - 네이버지도, 클릭 위치 marker 표시 (4) | 2020.09.12 |