문서 / 지도 뷰 / MapView와 상태

MapView의 라이프사이클과 이벤트

지도는 뷰와 상태 객체의 2가지로 다룹니다. 상태(mapViewState)가 카메라・디자인・조작의 창구이고, 뷰는 각 플랫폼의 UI 트리에 놓기만 하면 됩니다. 초기화는 공통의 단계를 밟고, 읽기 완료와 사용자 조작은 이벤트로 받습니다.

ANDROID
MapViewStateInterface
iOS
MapViewStateProtocol
REACT
MapViewStateInterface
플랫폼

01 · mapViewState

mapViewState는 지도 한 면에 대응하는 상태 객체입니다. 프로바이더마다 구현은 있지만, 공개되어 있는 멤버는 공통이며, 앱의 코드는 공통 인터페이스만 보고 있으면 충분합니다.

상태는 뷰보다 오래 삽니다. 뷰가 다시 만들어져도 카메라 위치나 디자인은 상태 쪽에 남기 때문에, 지도는 그대로 복원됩니다.

멤버
설명
id
상태의 식별자. 뷰의 재생성을 넘어 같은 지도를 계속 가리킵니다.
cameraPosition
현재의 카메라 위치. 표시 영역(visibleRegion)도 여기서 얻을 수 있습니다.
mapDesignType
현재의 지도 디자인. 대입하면 즉시 전환됩니다.
moveCameraTo()
카메라를 이동합니다. 시간을 넘기면 애니메이션합니다.
fitBounds()
지정한 범위가 들어가도록 카메라를 맞춥니다. 3개 플랫폼 모두 상태 객체의 메서드이며, 내부에서 컨트롤러에 위임합니다.
getMapViewHolder()
네이티브 지도 인스턴스를 감싼 홀더를 반환합니다.
MapViewState · Jetpack Compose
// state는 remember*로 만들면 화면 회전에도 같은 지도가 유지된다
val mapViewState =
    rememberGoogleMapViewState(
        mapDesign = GoogleMapDesign.Normal,
        cameraPosition = initCameraPosition,
    )

GoogleMapView(state = mapViewState, modifier = Modifier.fillMaxSize())

02 · 초기화의 라이프사이클

지도의 초기화는 어느 프로바이더에서도 같은 단계를 밟습니다. SDK의 읽기, 뷰의 생성, 지도 인스턴스의 생성, 그리고 타일의 렌더링 완료. 단계가 InitState로 공통화되어 있으므로, 「아직 조작해서는 안 되는 시기」를 프로바이더마다 외울 필요가 없습니다.

카메라 조작이나 오버레이의 추가는 MapCreated 이후에 받아들여지며, 내부에서 큐에 들어갑니다. 읽기 완료를 기다린 뒤에 무언가 하고 싶은 경우는 onMapLoaded를 씁니다.

그림 · InitState의 진행 순서
NotStarted
아직 아무것도 시작되지 않은 상태.
Initializing
초기화를 시작. API 키 검증과 SDK 로딩이 진행됩니다.
SdkInitialized
지도 SDK 본체의 로딩이 완료.
MapViewCreated
플랫폼의 뷰(MapView / UIView / DOM 요소)를 생성.
MapCreating
뷰 안에 지도 인스턴스를 생성 중.
MapCreated
지도 인스턴스를 쓸 수 있게 됨. 카메라 조작과 오버레이 등록이 반영되기 시작합니다.
MapLoaded
타일 렌더링까지 완료. onMapLoaded가 불리는 것은 이 단계입니다.
Failed
초기화에 실패(API 키 오류, 네트워크 단절 등).
단계는 Android・iOS・React에서 같은 열거형으로 정의되어 있습니다. 프로바이더를 바꿔도 무엇을 기다려야 하는지는 달라지지 않습니다.
01 sdkInitialize

지도 SDK를 읽어 들입니다. Web에서는 스크립트의 주입, 모바일에서는 초기화 처리. 실패하면 Failed로 진행합니다.

02 createHolder

네이티브 지도 인스턴스를 홀더로 감쌉니다. 이후 SDK 고유의 타입은 여기에 숨습니다.

03 createController

홀더를 컨트롤러로 감싸, 공통 API와 이벤트 배선을 준비합니다. 상태에 컨트롤러가 접속됩니다.

RESTORE

화면 회전・재생성을 넘어서

Android에서는 상태가 rememberSaveable로 저장되어, 화면 회전이나 설정 변경 뒤에도 카메라 위치와 디자인이 복원됩니다. 구성 변경 시에는 지도 뷰 자체를 파기하지 않고 재사용합니다.

TEARDOWN

파기될 때

진짜 파기(화면 이탈)에서는 컨트롤러가 가진 오버레이 관리나 타일 서버의 라우트, 코루틴 스코프를 한꺼번에 해제합니다. 프로바이더를 전환했을 때도 같은 경로로 이전 지도가 정리됩니다.

영상으로 보여 주는 구현 · Android + MapLibreKotlin · Jetpack Compose
var ready by remember { mutableStateOf(false) }

MapLibreMapView(
    state = mapViewState,
    // Called once, when the tiles have finished drawing
    onMapLoaded = { state ->
        ready = true
        state.fitBounds(routeBounds, padding = 48)
    },
) {
    // Declarations here are not lost before MapCreated — they are queued internally
    Marker(markerState)
}

if (!ready) {
    // Your own loading overlay, if you want one. The map does not need blocking
    Box(Modifier.fillMaxSize()) { CircularProgressIndicator() }
}
샘플 영상 · 초기화가 진행되는 모습
영상 미촬영지도가 생성되고 나서 조작을 받아들이기까지, InitState가 단계적으로 진행되는 모습. 읽는 중의 표시와 바뀌는 타이밍이 보이면 이해하기 쉬워집니다.

03 · 이벤트

뷰에 넘기는 핸들러는 3개 플랫폼에서 같은 구성입니다. 타입도 공통이며, 읽기 완료는 상태 객체, 탭은 좌표, 카메라 변화는 카메라 위치를 받습니다.

이벤트
받는 값
설명
onMapLoaded
MapViewState
지도의 읽기가 완료. 인수의 상태 객체를 보관해 이후의 조작에 쓰는 것이 정석입니다.
onMapClick
GeoPoint
지도의 탭. 정보 버블을 닫거나, 핀을 놓는 등에.
onMapLongClick
GeoPoint
길게 누르기.
onCameraMoveStart
MapCameraPosition
카메라 조작의 시작. 사용자 조작・프로그램 이동 양쪽에서 호출됩니다.
onCameraMove
MapCameraPosition
이동 중에 연속으로 호출됩니다. 표시 중인 좌표 표시 등에.
onCameraMoveEnd
MapCameraPosition
이동이 멈췄을 때. 데이터 재취득은 여기서 하는 것이 기본입니다.
GoogleMapView · 이벤트 받기
GoogleMapView(
    state = mapViewState,
    onMapLoaded = { state -> viewModel.onMapLoaded(state) },
    onMapClick = { point -> viewModel.onMapClick(point) },
    onCameraMove = { camera -> viewModel.onCameraChanged(camera) },
    onCameraMoveEnd = { camera -> viewModel.onCameraSettled(camera) },
) { /* markers, overlays */ }

카메라 계열의 이벤트는 상태 객체의 cameraPosition도 동시에 갱신하기 때문에, 핸들러를 붙이지 않아도 최신의 카메라는 항상 읽어 낼 수 있습니다. 이동 중의 재취득을 피하고 싶은 경우는 onCameraMoveEnd만 쓰세요.

04 · 네이티브로 가는 통로

공통 API로 부족할 때를 위해, 네이티브 지도 인스턴스로 내려가는 창구를 마련해 두었습니다. 평소에는 쓰지 않지만, 프로바이더 고유의 기능을 한 군데만 쓰고 싶은 경우의 통로입니다.

MapViewHolder

네이티브 지도를 감싸는 홀더

홀더는 플랫폼의 뷰와 지도 인스턴스 2가지를 가집니다. SDK 고유의 코드는 여기서부터 앞에 가둬 두고, 공통 코드에는 새지 않게 하는 구성입니다.

toScreenOffset / fromScreenOffset

좌표와 화면 위치의 변환

지리 좌표와 화면상의 픽셀 위치를 서로 변환할 수 있습니다. 지도 위에 독자적인 UI를 겹칠 때 씁니다.

홀더 꺼내기
// 네이티브 API가 정말 필요할 때만 꺼낸다
val holder = mapViewState.getMapViewHolder()
val nativeMap = holder?.map // GoogleMap / MapLibreMap / ...
val offset = holder?.toScreenOffset(point)

홀더를 쓴 코드는 프로바이더에 의존합니다. 공통인 채로 두고 싶은 부분과, 일부러 고유 API를 쓰는 부분을 의식해서 나누세요.

관련 페이지