카카오 지도
@react-native-kakao/map은 Android와 iOS에서 카카오 지도를 표시하고 카메라를 제어하는 Expo Module이에요. Core 패키지 없이 사용할 수 있어요.
설치와 앱 등록
Expo 52 이상, React Native 0.76 이상과 New Architecture가 필요해요. Android SDK 2.15.2와 iOS SDK 2.12.19를 사용해요. Expo Go에는 포함되지 않으므로 네이티브 앱을 빌드해야 해요. 일반 React Native 앱은 먼저 Expo Modules를 설치해요.
카카오 개발자 콘솔에서 앱의 네이티브 앱 키를 확인하고 카카오맵을 활성화해요. Android 패키지명과 서명 키 해시, iOS 번들 ID를 등록해요. Android 등록 안내와 iOS 등록 안내에서 플랫폼별 요구 사항을 확인할 수 있어요.
npx expo install @react-native-kakao/map expo-build-properties
Android 앱이 Kakao SDK를 다운로드할 수 있도록 app.json에 Maven 저장소를 추가해요. 기존 expo-build-properties 설정이 있으면 같은 항목에 합쳐요.
{
"expo": {
"plugins": [
[
"expo-build-properties",
{
"android": {
"extraMavenRepos": ["https://devrepo.kakao.com/nexus/repository/kakaomap-releases/"]
}
}
]
]
}
}
Expo 52에서 Kotlin 1.9.24와 Compose Compiler 1.5.15의 버전 불일치가 발생하면 위 android 설정에 "kotlinVersion": "1.9.25"를 추가해요. 최신 Expo에는 해당 버전을 강제하지 않아요.
npx expo prebuild
npx expo run:android
# 또는
npx expo run:ios
Expo 자동 등록이 네이티브 모듈을 연결해요. 별도 지도 config plugin이나 Core 초기화는 필요하지 않아요. 네이티브 프로젝트를 직접 관리한다면 Android 프로젝트의 공통 Maven 저장소에 위 URL을 추가하고, iOS에서 pod install을 실행해요.
지도 표시
초기화를 기다린 다음 지도를 마운트해요. 초기화 Promise는 SDK에 키를 전달했다는 뜻이고, 지도 인증과 뷰 준비는 onMapReady에서 확인해요. 같은 키로 반복 초기화할 수 있지만 다른 키로 변경할 수는 없어요.
import { useEffect, useRef, useState } from 'react';
import { Text } from 'react-native';
import { initializeKakaoMapSDK, KakaoMapView, type KakaoMapViewRef } from '@react-native-kakao/map';
export function MapScreen() {
const map = useRef<KakaoMapViewRef>(null);
const [initialized, setInitialized] = useState(false);
const [error, setError] = useState('');
useEffect(() => {
let active = true;
initializeKakaoMapSDK('YOUR_NATIVE_APP_KEY').then(
() => {
if (active) setInitialized(true);
},
(error: Error) => {
if (active) setError(error.message);
},
);
return () => {
active = false;
};
}, []);
if (error) return <Text>{error}</Text>;
if (!initialized) return null;
return (
<KakaoMapView
ref={map}
style={{ flex: 1 }}
initialCamera={{ latitude: 37.402001, longitude: 127.108678, zoomLevel: 15 }}
onMapReady={() => console.log('Map ready')}
onError={({ code, message }) => setError(`${code}: ${message}`)}
/>
);
}
지도에 너비와 높이가 생기도록 부모 레이아웃을 설정해요. Kakao 로고를 다른 뷰로 가리지 않아요.
카메라와 이벤트
카메라에는 latitude, longitude, 정수 zoomLevel이 필요해요. tilt와 rotation은 도 단위이며 생략하면 0이에요. 확대 단계와 기울기의 실제 표현 범위는 SDK와 지도 상태의 영향을 받아요. rotation은 정북 기준 카메라의 반시계 방향 회전각이에요. 90도에서는 북쪽이 화면 오른쪽을 향해요. getCamera()는 적용된 다섯 필드를 모두 반환해요. iOS의 중심 좌표는 여백을 제외한 지도 영역의 중심을 지리 좌표로 변환해 구하므로 요청한 좌표와 작은 오차가 생길 수 있어요.
최초에는 camera가 initialCamera보다 우선해요. initialCamera는 처음 속성을 받은 뒤 변경해도 적용하지 않아요. camera는 값이 바뀔 때만 이동하고, 같은 값의 새 객체를 전달하거나 속성을 제거해도 이동하지 않아요. 따라서 제스처나 ref로 이동한 뒤 동일 값으로 재렌더링해도 위치가 유지돼요. 준비 전에는 최신 camera만 보관해요.
지도 준비 후 ref를 호출할 수 있어요. 아래 Promise는 이동 명령을 전달하면 완료돼요. 애니메이션이 끝났는지는 onCameraIdle로 확인해요.
await map.current?.moveCamera(
{ latitude: 37.5665, longitude: 126.978, zoomLevel: 16, tilt: 30, rotation: 90 },
{ duration: 500 },
);
const position = await map.current?.getCamera();
duration은 0부터 2,147,483,647까지의 정수 밀리초이며 생략하거나 0이면 즉시 이동해요. tilt는 0~90도를 받고, 반환되는 rotation은 0 이상 360 미만이에요.
updateCamera()는 나머지 카메라 값을 유지하면서 위치, 확대 단계 또는 회전·기울기를 바꿔요. fit은 전달한 좌표들을 현재 지도 여백 안에 표시하도록 카메라를 맞춰요.
await map.current?.updateCamera({
type: 'position',
position: { latitude: 37.5665, longitude: 126.978 },
});
await map.current?.updateCamera({ type: 'zoom', zoomLevel: 18 });
await map.current?.updateCamera({ type: 'orientation', rotation: 90, tilt: 30 }, { duration: 500 });
await map.current?.updateCamera({
type: 'fit',
coordinates: [
{ latitude: 37.5665, longitude: 126.978 },
{ latitude: 37.402001, longitude: 127.108678 },
],
});
| 이벤트 | 전달 값 |
|---|---|
onMapReady | 지도 인증과 뷰 준비 완료. 인자 없음 |
onError | { code, message } |
onCameraMoveStart | { reason: 'gesture' | 'developer' } |
onCameraIdle | { reason, camera }. 이동이 멈춘 실제 카메라 |
onMapClick | { latitude, longitude, x, y }. x, y는 지도 좌측 상단 기준 논리 픽셀 |
onMapLongClick | { latitude, longitude }. 지도 지형을 길게 누른 위치 |
onTerrainClick | { latitude, longitude }. 지형 클릭. Android와 iOS |
onViewportClick | { latitude, longitude, x, y }. viewport 클릭. Android 전용 |
onVisibilityChanged | { visible }. 네이티브 지도 표시 상태 변경. Android 전용 |
onFocusChanged | { focused }. 네이티브 지도 focus 변경. iOS 전용 |
onPoiClick | { id, layerId, latitude, longitude }. POI ID와 해당 지도 레이어 ID |
onCompassClick | 컴퍼스 클릭. 인자 없음 |
onMapTypeChanged | { viewInfoName }. 비동기 지도 구성 변경 완료 |
onPaddingChanged | { top, right, bottom, left }. 변경된 지도 여백 |
onViewportChanged | { x, y, width, height }. 변경된 실제 viewport. Android와 iOS |
연속 카메라 변경 이벤트는 제공하지 않아요. 앱 비활성화와 화면 분리 시 렌더링을 멈추고, 복귀하면 재개해요. 마운트 해제 시 엔진을 해제해요.
지도 설정과 좌표 변환
options는 전달한 필드를 적용해요. 생략한 필드는 SDK의 현재 값을 유지해요. 준비 전에는 필드별 최신 값을 보관하고, 준비 후에는 변경된 값만 적용해요. mapType과 viewInfo는 함께 전달할 수 없어요. viewInfo와 customOverlays는 별도 이용 협의가 필요한 지도 데이터에 사용해요.
<KakaoMapView
options={{
mapType: 'normal',
overlays: ['bicycleRoad'],
language: 'ko',
poi: { visible: true, clickable: true, scale: 'regular' },
buildingScale: 1,
padding: { top: 20, right: 20, bottom: 80, left: 20 },
zoomRange: { min: 6, max: 21 },
gestures: { pan: true, rotate: false },
compass: { visible: true, position: { gravity: 'topRight', x: 20, y: 20 } },
scaleBar: {
visible: true,
autoHide: true,
fade: { inDuration: 200, outDuration: 400, retentionDuration: 1000 },
},
logo: { gravity: 'bottomLeft', x: 12, y: 12 },
}}
/>
mapType은 normal과 skyview, overlays는 roadviewLine, hillShading, bicycleRoad, hybrid를 지원해요. 언어는 getMapInfo().supportedLanguages에 포함된 값을 사용해요. POI 크기는 small, regular, large, xLarge이고, 건물 높이 비율은 0~1이에요. 제스처별 설정은 MapGesture 타입에서 확인할 수 있어요.
여백·위젯 위치·화면 좌표는 논리 픽셀, 축척 표시의 시간은 정수 밀리초예요. 로고 위치는 바꿀 수 있지만 숨길 수는 없어요. 비동기로 적용되는 지도 구성과 여백은 각각 onMapTypeChanged, onPaddingChanged에서 완료를 확인해요.
const info = await map.current?.getMapInfo();
const coordinate = await map.current?.screenToCoordinate({ x: 100, y: 100 });
const point = await map.current?.coordinateToScreen({ latitude: 37.402001, longitude: 127.108678 });
await map.current?.clearCache();
getMapInfo()는 지도 종류·확대 범위·여백·영역 크기·언어와 지원 기능을 반환해요. 화면 좌표는 지도 영역 안에 있어야 해요. iOS SDK는 지리 좌표를 화면 좌표로 바꾸는 API를 제공하지 않으므로 capabilities.coordinateToScreen이 false이고 coordinateToScreen()을 호출하면 ERR_KAKAO_MAP_UNSUPPORTED_PLATFORM_FEATURE로 거절해요. clearCache()는 SDK의 지도 캐시를 지우며 이후 필요한 타일을 다시 받아요.
라벨과 마커
labels는 지도 위의 마커, 대량 표시에 적합한 LOD 라벨, 경로를 따르는 텍스트를 하나의 LabelScene으로 관리해요. 스타일과 레이어를 먼저 정의하고, 각 라벨이 해당 ID를 참조하도록 구성해요.
텍스트 라벨 표시
처음에는 이미지 없이 텍스트 스타일과 일반 라벨 하나로 시작할 수 있어요. clickable은 레이어와 라벨 양쪽에서 활성화해야 클릭 이벤트를 받아요.
import { KakaoMapView, type LabelScene } from '@react-native-kakao/map';
const placeLabels = {
styles: [
{
id: 'place-text',
levels: [
{
level: 0,
textLines: [
{
fontSize: 14,
color: '#111827FF',
strokeColor: '#FFFFFFFF',
strokeWidth: 2,
},
],
},
],
},
],
layers: [{ id: 'places', zOrder: 1000, clickable: true }],
labels: [
{
id: 'seoul-city-hall',
layerId: 'places',
styleId: 'place-text',
coordinate: { latitude: 37.5665, longitude: 126.978 },
text: [{ value: '서울시청', styleIndex: 0 }],
clickable: true,
},
],
} satisfies LabelScene;
<KakaoMapView
labels={placeLabels}
onLabelClick={({ id, layerId, kind, coordinate }) => {
console.log(id, layerId, kind, coordinate);
}}
/>;
styles의 levels는 확대 단계별 표현을 정의해요. 일반 라벨은 layers와 labels, 대량 표시에 적합한 LOD 라벨은 lodLayers와 lodLabels, 경로 텍스트는 pathTextStyles와 pathLabels를 사용해요. 한 장면 안의 모든 ID는 비어 있지 않고 종류별로 중복되지 않아야 하며, 스타일·레이어 참조도 같은 장면 안에 존재해야 해요.
아이콘 URI와 표시 단위
아이콘을 추가할 때는 React Native 정적 에셋을 URI로 해석한 뒤 MapImageSource에 전달해요. file:, http:, https: URI도 사용할 수 있어요.
import { Image } from 'react-native';
import type { LabelStyle } from '@react-native-kakao/map';
const pin = Image.resolveAssetSource(require('./pin.png'));
const pinStyle = {
id: 'place-pin',
levels: [
{
level: 0,
image: { uri: pin.uri, width: 32, height: 40 },
anchor: { x: 0.5, y: 1 },
},
],
} satisfies LabelStyle;
이미지 크기, 글꼴 크기, 선 두께, 여백과 배지·픽셀 오프셋은 Android에서 dp, iOS에서 points인 논리 단위예요. 단, movePath.cornerRadius와 movePath.jumpThreshold는 SDK 공식 문서에 단위가 없어서 변환하지 않고 raw 숫자로 전달해요. 이 두 값은 화면·이미지·스타일의 논리 단위 규칙을 적용하지 않아요. 각도는 도, 시간은 정수 밀리초로 전달해요. 색상은 #RRGGBB 또는 #RRGGBBAA 형식이에요. 이미지 다운로드나 디코딩이 실패하면 장면을 적용하지 않고 ERR_KAKAO_MAP_IMAGE_LOAD를 전달해요.
SDK가 정수 픽셀만 받는 글꼴 크기·테두리·자간은 배율을 적용한 뒤 가장 가까운 정수 픽셀로 반올림해요. lineSpacing은 길이가 아닌 행간 배율로, 1은 100%, 2는 200%이며 기본값은 1이에요.
Android 자간은 변환 후 최소 -2픽셀이어야 해요. 더 작은 값은 SDK 텍스트 스타일이 적용하지 않으므로 오류로 거절해요.
레이어의 zOrder를 생략하면 두 플랫폼 모두 1000을 사용해요. 값이 클수록 같은 종류의 라벨 레이어에서 위에 그려져요. SDK 내부 레이어와 충돌하지 않도록 다음 사용자 범위만 사용해요.
zOrder 범위 | 용도 |
|---|---|
1000~1999 | 사용자 라벨 |
3000~3999 | 사용자 라벨 |
5000 이상 | 사용자 라벨 |
| 나머지 범위 | 지도 엔진 예약 영역 |
스냅샷 교체와 런타임 명령
LabelScene은 부분 패치가 아니라 전체 스냅샷이에요. 새 labels 값을 적용하면 기존 애니메이션, 추적, 레이어, 라벨과 배지를 제거하고 새 장면으로 교체해요. 빈 장면 labels={{}}을 전달하거나 한 번 사용한 labels 속성을 제거하면 모든 사용자 라벨을 지워요. 이미지 로딩 중 더 새로운 장면을 전달하면 이전 적용을 취소하고 최신 스냅샷만 적용해요. 대체된 장면의 취소는 오류 이벤트를 발생시키지 않으며, 현재 장면의 입력·이미지 처리 실패는 onError로 전달돼요.
지도가 준비된 뒤 runLabelCommand()로 현재 장면의 라벨을 바꿀 수 있어요. 명령은 장면을 교체하지 않으며 존재하는 안정적인 ID를 사용해요.
await map.current?.runLabelCommand({
type: 'move',
id: 'seoul-city-hall',
coordinate: { latitude: 37.5657, longitude: 126.9769 },
duration: 400,
});
await map.current?.runLabelCommand({
type: 'show',
id: 'seoul-city-hall',
visible: false,
});
const capabilities = await map.current?.getLabelCapabilities();
const state = await map.current?.getLabelState();
LabelCommand는 표시, 위치·경로 이동, 회전, 크기, 위치·변환 공유, 배지 표시, 애니메이션 시작·중지와 카메라 추적을 다뤄요. 플랫폼별로 지원하지 않는 명령은 자동으로 흉내 내지 않고 ERR_KAKAO_MAP_LABEL_UNSUPPORTED로 거절해요. 실행 전 getLabelCapabilities()에서 사용할 기능을 확인해요.
| 이벤트 | 전달 값 |
|---|---|
onLabelClick | { id, layerId, kind, coordinate }. kind는 label 또는 lodLabel |
onLabelAnimationStopped | { id, finished }. 자연스럽게 끝나면 true, 명시적으로 중지하면 false |
onLabelsChanged | 장면 적용 후의 LabelRuntimeState |
getLabelState()는 SDK에 실제로 남아 있는 라벨의 ID·종류·표시 상태와 현재 장면에서 실행할 수 있는 애니메이션 정의 ID를 반환해요. onLabelsChanged는 장면을 SDK에 전달한 직후 조회한 상태이며 렌더링 완료를 뜻하지 않아요. SDK의 표시 상태는 다음 프레임에서 바뀔 수 있으므로 이후 상태가 필요하면 다시 조회해요.
tracking.source가 native인 iOS는 SDK의 실제 추적 상태를 읽어요. Android SDK는 추적 상태 getter를 제공하지 않으므로 source: 'managed'와 함께 이 모듈이 요청한 추적 상태를 반환해요. 관리 상태를 SDK에서 다시 읽은 값으로 해석하면 안 돼요. removeAtStop으로 대상이 제거된 애니메이션은 다시 시작할 수 없으며 장면을 새로 적용해야 해요.
주요 플랫폼 차이는 기능 플래그로도 확인할 수 있어요.
| 기능 | Android | iOS |
|---|---|---|
| 경로 텍스트 구현 | polylineLabel | waveText |
경로 텍스트 rank | 미지원 | 지원 |
| 등록한 경로 스타일 변경·제거 | 지원 | 미지원 |
LOD 라벨 autoMove | 미지원 | 지원 |
| 자동 이동 시간 지정 | 지원 | 미지원 |
| 직접 크기 변경 명령 | 지원 | 미지원, 크기 애니메이션을 사용 |
| 경쟁 예외 레이어 | 지원 | 미지원 |
| 추적 상태 출처 | 모듈 관리 상태 | SDK 실제 상태 |
두 플랫폼 모두 LOD 라벨의 위치 변경은 지원하지 않아요. drop 이외의 애니메이션은 repeatCount를 1로 사용해요. iOS는 등록한 경로 스타일 정의를 바꾸거나 제거할 수 없으므로 새 스타일 ID를 사용해야 해요. 정확한 현재 값은 설치된 네이티브 SDK에 맞춰 반환되는 getLabelCapabilities()를 기준으로 판단해요.
일반 라벨의 coordinate는 두 플랫폼 모두 SDK에서 읽은 현재 좌표예요. iOS SDK는 LOD 라벨의 현재 좌표 getter를 제공하지 않으므로 kind: 'lodLabel'인 상태에서는 coordinate가 생략될 수 있어요.
iOS에서도 위치·변환 공유를 해제한 라벨은 move 명령으로 독립적으로 이동할 수 있어요.
고급 카메라 명령
executeCameraCommand()는 높이 기반 이동, 상대 변환, 영역 맞춤, 표시 가능 여부와 기준점 잠금처럼 기본 카메라 API보다 세밀한 기능을 다뤄요. 먼저 기능을 조회하고 지원되는 필드만 사용해요.
const result = await map.current?.executeCameraCommand({ type: 'getCapabilities' });
await map.current?.executeCameraCommand({
type: 'moveToHeight',
camera: {
latitude: 37.402001,
longitude: 127.108678,
height: 1200,
tilt: 30,
rotation: 45,
},
animation: { duration: 500, autoElevation: true },
operationId: 'camera-1',
});
operationId를 지정한 명령은 플랫폼이 실제 완료 콜백을 지원할 때 onCameraOperation으로 { operationId, status, camera? }를 전달해요. 지원하지 않는 플랫폼은 dispatch를 완료로 꾸미지 않고 해당 필드를 거절해요. status: 'superseded'는 더 새로운 카메라 동작이 기존 작업을 대체했다는 뜻이에요.
getState는 높이·확대 단계·기울기·회전을 포함한 실제 카메라 상태를 반환해요. 각도는 도, 애니메이션 시간은 정수 밀리초, fit.padding과 canShow.rect는 논리 단위예요. height는 네이티브 지도 좌표계의 카메라 높이이므로 화면 dp나 points로 변환하지 않아요.
도형과 경로
ShapeScene과 RouteScene은 전체 장면 스냅샷이에요. ref 명령은 현재 장면의 ID를 변경하거나 실제 상태·개수를 조회해요. 장면을 비우려면 각각 applyShapes({}), applyRoutes({})를 호출해요.
도형
도형은 스타일, 레이어, 도형 순서로 참조해요. 다음 예시는 기준 좌표를 중심으로 지상 반지름 30미터인 원을 표시해요.
import type { ShapeScene } from '@react-native-kakao/map';
const circleScene = {
styleSets: [
{
type: 'polygon',
id: 'area-style',
styles: [{ levels: [{ level: 0, fillColor: '#22C55E66', strokeWidth: 2 }] }],
},
],
layers: [{ id: 'areas' }],
shapes: [
{
id: 'circle',
layerId: 'areas',
styleSetId: 'area-style',
geometry: {
type: 'geographicCircle',
center: { latitude: 37.402001, longitude: 127.108678 },
radius: 30,
},
},
],
} satisfies ShapeScene;
const counts = await map.current?.applyShapes(circleScene);
const state = await map.current?.executeShapeCommand({ type: 'getState', id: 'circle' });
모델 ShapePoint의 x, y와 모델 primitive의 치수는 모델 공간의 미터예요. iOS SDK가 각도 좌표를 받는 지점은 모듈이 미터 값을 변환해요. 지리 primitive의 치수는 지상 미터예요. 같은 수치를 넣어도 두 형태의 지상 거리가 같다고 보장하지 않으며, iOS에서는 같은 값의 모델 원과 지리 원이 다른 크기로 표시될 수 있어요. 지상 거리를 지정하려면 geographicCircle·geographicRectangle을 사용해요. 스타일 폭·테두리와 wave 반경은 Android dp/iOS points인 논리 단위예요. orientation과 arc 각도는 도이며, arc는 정북 기준 시계 방향이에요. 시간은 정수 밀리초예요.
getCapabilities에서 위치·방향·z-order 변경, 레이어 pass, wave 대상과 완료 이벤트 지원 여부를 확인해요. iOS는 모델 방향을 지원하지만 Android는 지원하지 않아요. Android는 지리 도형 위치와 z-order 변경을 지원하지만 해당 현재 값 getter가 없어 상태에 null을 반환할 수 있어요.
모델 polygon에 연결한 shareShapeTransform은 위치 공유와 해제만 성공적으로 검증됐고, 공유 회전에는 렌더링 문제가 있어요. Android SDK 2.15.2의 ARM 에뮬레이터와 iOS SDK 2.12.19의 Simulator에서 소스 라벨을 90도로 애니메이션 회전하면 도형이 뷰포트 전체를 덮는 현상이 재현됐어요. Android에서는 모듈을 거치지 않는 SDK 직접 호출에서도 재현됐으며, 즉시 회전 setter를 사용하면 도형의 회전 자체가 전달되지 않았어요. 이 SDK 조합에서 모델 polygon의 공유 회전에 의존하지 마세요.
경로
경로도 스타일·레이어·경로를 한 장면에 정의해요. setProgress는 표시 진행률을 바꾸고, getState와 getCounts는 현재 네이티브 객체를 조회해요.
await map.current?.applyRoutes({
styleSets: [
{
id: 'route-style',
styles: [{ levels: [{ level: 0, width: 8, color: '#2563EBFF' }] }],
},
],
layers: [{ id: 'routes' }],
routes: [
{
id: 'route-1',
layerId: 'routes',
styleSetId: 'route-style',
segments: [
{
coordinates: [
{ latitude: 37.402001, longitude: 127.108678 },
{ latitude: 37.404, longitude: 127.112 },
],
},
],
},
],
});
await map.current?.executeRouteCommand({
type: 'setProgress',
id: 'route-1',
progress: 0.5,
mode: 'fill',
duration: 400,
});
경로 폭·테두리·패턴 간격은 논리 단위이고 시간은 정수 밀리초예요. onRouteProgress는 실제 SDK 진행률 측정값이 아니라 최신 setProgress 요청 전환의 완료 알림이에요. 전달 값의 source는 항상 'requested'예요. duration: 0이면 네이티브 명령 전달 직후 한 번 발생해요. iOS SDK는 실제 progress getter를 제공하지 않으므로 getProgress를 지원하지 않지만, getRequestedProgress로 마지막 요청값을 구분해서 조회할 수 있어요.
두 플랫폼의 네이티브 경로 애니메이터는 반복 횟수 1만 지원해요. Android SDK는 고유한 도형·경로 스타일과 애니메이터 등록을 지도 엔진이 끝날 때까지 유지해요. 같은 정의는 모듈이 재사용하지만, 계속 새로운 정의를 만들면 엔진 수명 동안 등록 수가 늘 수 있어요.
GUI와 화면 어둡게 표시
applyGui()는 인포윈도우와 화면 고정 위젯의 트리를 적용해요. 레이아웃·이미지·텍스트는 두 플랫폼에서 사용할 수 있고, 버튼·animated image·인포윈도우 drop 애니메이션은 iOS 전용이에요.
await map.current?.applyGui({
infoWindows: [
{
id: 'info',
position: { latitude: 37.402001, longitude: 127.108678 },
body: { type: 'text', id: 'title', text: '판교', textSize: 14, color: '#111827FF' },
},
],
});
const gui = await map.current?.getGuiStatus();
getGuiStatus().capabilities를 확인한 뒤 startGuiAnimation, controlAnimatedImage와 플랫폼 전용 컴포넌트를 사용해요. Android는 클릭 가능한 임의 컴포넌트를 지원하지만, iOS의 컴포넌트 탭은 button에서 발생해요. iOS는 인포윈도우 auto-move 시간을 지정할 수 없어요. 이미지·글꼴·테두리·패딩·화면 오프셋과 drop 높이는 논리 단위, 회전은 도, 시간은 정수 밀리초예요.
KakaoMapsSDK 2.12.19를 사용하는 iOS Simulator에서는 버튼의 기본 이미지와 탭 이벤트를 확인했지만, 누르는 동안 pressedSource 이미지로 전환되는 모습은 확인하지 못했어요. 이 제한은 button을 인포윈도우 본문으로 직접 사용하거나 이미지의 자식으로 구성한 경우에 모두 관찰됐어요. button: true를 눌림 이미지의 표시 보장으로 해석하면 안 돼요.
applyDimScreen()은 지도를 반투명 색으로 덮고 지정한 polygon 영역을 강조해요. 강조 도형은 Shape의 polygon geometry와 style set을 그대로 사용해요.
await map.current?.applyDimScreen({
enabled: true,
color: '#11182799',
cover: 'mapAndLabel',
styleSets: [],
highlights: [],
});
const dim = await map.current?.getDimScreenStatus();
getDimScreenStatus().capabilities에서 route 포함, 모델 강조 방향, 애니메이션과 상태 getter 지원 여부를 확인해요. iOS는 mapAndRoute와 dim z-order를 지원하지 않아요. Android는 dim 설정과 애니메이터 실행 상태 getter가 없어 관련 상태가 null일 수 있어요.
지도 유틸리티
executeUtilityCommand()는 POI 카테고리, 탭 간격, 카메라 애니메이션, 지도 활성·표시 상태, 실제 viewport, 버전, 좌표계 변환, 글꼴 등록과 캐시를 다뤄요. getCapabilities 결과에 있는 기능만 호출해요.
const utility = await map.current?.executeUtilityCommand({ type: 'getCapabilities' });
await map.current?.executeUtilityCommand({
type: 'setCameraAnimationEnabled',
enabled: false,
});
const viewport = await map.current?.executeUtilityCommand({ type: 'getViewport' });
const converted = await map.current?.executeUtilityCommand({
type: 'convertCoordinate',
from: 'wgs84',
to: 'wtm',
coordinate: { x: 127.108678, y: 37.402001 },
});
getViewport는 양쪽 플랫폼에서 SDK의 현재 { x, y, width, height }를 논리 화면 단위로 읽어요. setViewport는 Android만 지원해요. 카메라 애니메이션 활성화는 양쪽에서 바꿀 수 있지만, 현재 활성화 값은 iOS의 getState에서만 읽을 수 있어요. Android에서는 getter가 없는 설정을 요청값으로 채워 넣지 않아요. setter Promise는 SDK에 명령을 전달했다는 뜻이며 실제 상태 readback이 아니에요.
Android에서는 requestPoiState()가 SDK의 비동기 응답을 기다린 뒤 POI의 visible, clickable, scale, language를 반환해요. 3초 안에 응답하지 않으면 ERR_KAKAO_MAP_POI_STATE_TIMEOUT으로 거절해요. iOS SDK에는 같은 비동기 조회가 없어요.
Android의 registerFont는 앱의 컴파일된 res/font 리소스를 지도 SDK에 등록해요. resourceName은 소문자로 시작하고 소문자·숫자·밑줄만 포함해야 하며, 선택적인 name으로 SDK 등록 이름을 지정할 수 있어요. iOS SDK는 이 등록 방식을 지원하지 않아요.
WGS84에서 x는 경도, y는 위도예요. WTM·WCong·Kakao 좌표는 SDK 좌표계 단위이며 화면 배율을 적용하지 않아요. iOS는 Kakao 좌표계와 memory/view-info cache, Android는 engine version을 추가로 지원해요.
오류와 플랫폼 범위
ref 명령은 지도 준비 전에 대기하지 않고 거절돼요. Promise 오류의 code, message와 onError를 처리해요.
| 코드 | 의미 |
|---|---|
ERR_KAKAO_MAP_NOT_INITIALIZED | SDK를 초기화하지 않음 |
ERR_KAKAO_MAP_NOT_READY | 지도 준비 전 명령 호출 |
ERR_KAKAO_MAP_CAMERA_TIMEOUT | Android 카메라 조회 응답이 3초 안에 오지 않음 |
ERR_KAKAO_MAP_VIEW_DESTROYED | 해제된 지도 접근 |
ERR_KAKAO_MAP_ALREADY_INITIALIZED | 다른 키로 재초기화 시도 |
ERR_KAKAO_MAP_INVALID_ARGUMENT | 빈 앱 키·잘못된 카메라·이동 시간 |
ERR_KAKAO_MAP_VIEW_CREATION | 네이티브 지도 생성 실패 |
ERR_KAKAO_MAP_UNSUPPORTED_PLATFORM | 웹에서 지도 기능 사용 |
ERR_KAKAO_MAP_LABEL_INVALID_ARGUMENT | 중복 ID, 잘못된 좌표·스타일·애니메이션 입력 |
ERR_KAKAO_MAP_LABEL_NOT_FOUND | 참조한 라벨·레이어·스타일·배지·애니메이션 ID가 없음 |
ERR_KAKAO_MAP_LABEL_UNSUPPORTED | 현재 플랫폼 SDK가 해당 라벨 기능을 지원하지 않음 |
ERR_KAKAO_MAP_IMAGE_LOAD | 라벨 이미지 다운로드 또는 디코딩 실패 |
ERR_KAKAO_MAP_SHAPE_INVALID_ARGUMENT | 잘못된 도형 장면·geometry·style 입력 |
ERR_KAKAO_MAP_SHAPE_NOT_FOUND | 참조한 도형 또는 애니메이션 ID가 없음 |
ERR_KAKAO_MAP_SHAPE_UNSUPPORTED | 현재 SDK가 해당 도형 기능을 지원하지 않음 |
ERR_KAKAO_MAP_ROUTE_INVALID_ARGUMENT | 잘못된 경로 장면·segment·progress 입력 |
ERR_KAKAO_MAP_ROUTE_NOT_FOUND | 참조한 경로 또는 애니메이션 ID가 없음 |
ERR_KAKAO_MAP_ROUTE_UNSUPPORTED | 현재 SDK가 해당 경로 기능을 지원하지 않음 |
ERR_KAKAO_MAP_GUI_INVALID_ARGUMENT | 잘못된 GUI 트리·크기·색상 입력 |
ERR_KAKAO_MAP_GUI_NOT_FOUND | 참조한 인포윈도우·위젯·애니메이션 ID가 없음 |
ERR_KAKAO_MAP_DIM_INVALID_ARGUMENT | 잘못된 dim cover·강조 도형·애니메이션 입력 |
ERR_KAKAO_MAP_UNSUPPORTED_PLATFORM_FEATURE | 현재 플랫폼 SDK에 없는 네이티브 기능 |
ERR_KAKAO_MAP_COORDINATE_CONVERSION_FAILED | SDK 좌표 변환 결과를 만들 수 없음 |
ERR_KAKAO_MAP_VERSION_UNAVAILABLE | 요청한 SDK 또는 엔진 버전 정보를 읽을 수 없음 |
ERR_KAKAO_MAP_POI_STATE_TIMEOUT | Android POI 상태 응답이 3초 안에 오지 않음 |
ERR_KAKAO_MAP_FONT_RESOURCE_NOT_FOUND | Android 컴파일 글꼴 리소스를 찾을 수 없음 |
SDK 오류는 SDK의 코드와 메시지를 유지해요. 지도 인증 오류는 초기화 Promise가 아니라 onError로 전달돼요. 인증 실패 후 자동 재시도는 하지 않아요. 키·앱 등록 설정이나 네트워크 문제를 해결한 다음 뷰를 다시 마운트해요. 앱 키 자체를 변경해야 한다면 앱 프로세스를 다시 시작해요.
Android의 getCamera()는 렌더러의 비동기 응답을 받아 이동 중인 위치를 조회해요. 3초 안에 응답하지 않으면 ERR_KAKAO_MAP_CAMERA_TIMEOUT으로 거절하고, 응답 전에 뷰가 해제되면 ERR_KAKAO_MAP_VIEW_DESTROYED로 거절해요.
도형·경로·GUI·dim·유틸리티는 잘못된 입력, 찾을 수 없는 ID, 취소·대체, 이미지 로딩과 플랫폼 미지원 상황을 도메인별 코드로 구분해요. 모든 ref Promise에서 code와 message를 함께 처리하고, capability가 false인 기능은 성공을 흉내 내지 않는다고 가정해요.
웹에서 패키지를 import할 수 있지만 네이티브 지도 기능은 지원하지 않아요. Android 지도 SDK에는 x86_64 바이너리가 없으므로 ARM 에뮬레이터나 실기기를 사용해요.
SDK는 여러 지도 엔진의 동시 렌더링을 보장하지 않아요. 한 번에 하나의 지도를 표시하는 구성을 사용해요. 자세한 내용은 카카오의 다중 엔진 안내를 확인해요.