diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapCameraIdleEvent.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapCameraIdleEvent.kt new file mode 100644 index 00000000..95aa1202 --- /dev/null +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapCameraIdleEvent.kt @@ -0,0 +1,42 @@ +package com.mjstudio.reactnativenavermap.event + +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.WritableMap +import com.facebook.react.uimanager.events.Event + +class NaverMapCameraIdleEvent( + surfaceId: Int, + viewId: Int, + private val latitude: Double, + private val longitude: Double, + private val zoom: Double, + private val tilt: Double, + private val bearing: Double, + private val regionLatitude: Double, + private val regionLongitude: Double, + private val regionLatitudeDelta: Double, + private val regionLongitudeDelta: Double, +) : Event(surfaceId, viewId) { + override fun getEventName(): String = EVENT_NAME + + override fun canCoalesce(): Boolean = false + + override fun getCoalescingKey(): Short = 0 + + override fun getEventData(): WritableMap = + Arguments.createMap().apply { + putDouble("latitude", latitude) + putDouble("longitude", longitude) + putDouble("zoom", zoom) + putDouble("tilt", tilt) + putDouble("bearing", bearing) + putDouble("regionLatitude", regionLatitude) + putDouble("regionLongitude", regionLongitude) + putDouble("regionLatitudeDelta", regionLatitudeDelta) + putDouble("regionLongitudeDelta", regionLongitudeDelta) + } + + companion object { + const val EVENT_NAME = "topCameraIdle" + } +} diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapView.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapView.kt index 00e3ac26..d742053e 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapView.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapView.kt @@ -8,6 +8,7 @@ import com.airbnb.android.react.maps.ViewAttacherGroup import com.facebook.react.bridge.ReadableMap import com.facebook.react.uimanager.ThemedReactContext import com.mjstudio.reactnativenavermap.event.NaverMapCameraChangeEvent +import com.mjstudio.reactnativenavermap.event.NaverMapCameraIdleEvent import com.mjstudio.reactnativenavermap.event.NaverMapInitializeEvent import com.mjstudio.reactnativenavermap.event.NaverMapOptionChangeEvent import com.mjstudio.reactnativenavermap.event.NaverMapTapEvent @@ -60,9 +61,7 @@ class RNCNaverMapView( it.addOnCameraChangeListener { reason, animated -> reactContext.emitEvent(reactTag) { surfaceId, reactTag -> - val bounds = it.coveringBounds - NaverMapCameraChangeEvent( surfaceId, reactTag, @@ -85,6 +84,25 @@ class RNCNaverMapView( } } + it.addOnCameraIdleListener { + reactContext.emitEvent(reactTag) { surfaceId, reactTag -> + val bounds = it.coveringBounds + NaverMapCameraIdleEvent( + surfaceId, + reactTag, + it.cameraPosition.target.latitude, + it.cameraPosition.target.longitude, + it.cameraPosition.zoom, + it.cameraPosition.tilt, + it.cameraPosition.bearing, + bounds.southLatitude, + bounds.westLongitude, + bounds.northLatitude - bounds.southLatitude, + bounds.eastLongitude - bounds.westLongitude, + ) + } + } + it.setOnMapClickListener { pointF, latLng -> reactContext.emitEvent(reactTag) { surfaceId, reactTag -> NaverMapTapEvent( diff --git a/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapViewManager.kt b/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapViewManager.kt index 076488f7..3722ff41 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapViewManager.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapViewManager.kt @@ -13,6 +13,7 @@ import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.annotations.ReactProp import com.mjstudio.reactnativenavermap.RNCNaverMapViewManagerSpec import com.mjstudio.reactnativenavermap.event.NaverMapCameraChangeEvent +import com.mjstudio.reactnativenavermap.event.NaverMapCameraIdleEvent import com.mjstudio.reactnativenavermap.event.NaverMapClusterLeafTapEvent import com.mjstudio.reactnativenavermap.event.NaverMapCoordinateToScreenEvent import com.mjstudio.reactnativenavermap.event.NaverMapInitializeEvent @@ -116,6 +117,7 @@ class RNCNaverMapViewManager : RNCNaverMapViewManagerSpec { - console.log(`Region: ${formatJson(region)}`); + // console.log(`Region: ${formatJson(region)}`); // eslint-disable-next-line @typescript-eslint/no-shadow const cities = getCitiesByRegion(region); - console.log(cities.length); + // console.log(cities.length); setCities(cities); }); }} + onCameraIdle={({ region }) => { + console.log('idle', region); + }} onTapMap={(args) => console.log(`Map Tapped: ${formatJson(args)}`)} clusters={clusters} // locationOverlay={{ diff --git a/ios/RNCNaverMapViewImpl.mm b/ios/RNCNaverMapViewImpl.mm index 6d6af775..18e18051 100644 --- a/ios/RNCNaverMapViewImpl.mm +++ b/ios/RNCNaverMapViewImpl.mm @@ -254,6 +254,25 @@ - (void)mapView:(NMFMapView*)mapView cameraIsChangingByReason:(NSInteger)reason }); } +- (void)mapViewCameraIdle:(NMFMapView*)mapView { + if (!_rncParent.emitter) + return; + NMFCameraPosition* pos = mapView.cameraPosition; + NMGLatLngBounds* bounds = mapView.coveringBounds; + + _rncParent.emitter->onCameraIdle({ + .latitude = pos.target.lat, + .longitude = pos.target.lng, + .zoom = pos.zoom, + .tilt = pos.tilt, + .bearing = pos.heading, + .regionLatitude = bounds.southWestLat, + .regionLongitude = bounds.southWestLng, + .regionLatitudeDelta = bounds.northEastLat - bounds.southWestLat, + .regionLongitudeDelta = bounds.northEastLng - bounds.southWestLng, + }); +} + - (void)mapView:(NMFMapView*)mapView didTapMap:(NMGLatLng*)latlng point:(CGPoint)point { if (!_rncParent.emitter) return; diff --git a/ios/RNCNaverMapViewManager.mm b/ios/RNCNaverMapViewManager.mm index edc3140d..ffde340b 100644 --- a/ios/RNCNaverMapViewManager.mm +++ b/ios/RNCNaverMapViewManager.mm @@ -57,6 +57,7 @@ + (BOOL)requiresMainQueueSetup { RCT_EXPORT_VIEW_PROPERTY(onInitialized, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onOptionChanged, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onCameraChanged, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onCameraIdle, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onTapMap, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onScreenToCoordinate, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onCoordinateToScreen, RCTDirectEventBlock) diff --git a/src/component/NaverMapView.tsx b/src/component/NaverMapView.tsx index c341aa76..8f208bf7 100644 --- a/src/component/NaverMapView.tsx +++ b/src/component/NaverMapView.tsx @@ -445,6 +445,20 @@ export interface NaverMapViewProps extends ViewProps { } ) => void; + /** + * 카메라의 움직임이 끝나 대기 상태가 되면 카메라 대기 이벤트가 발생합니다. + * + * 카메라는 다음과 같은 시점에 대기 상태가 된 것으로 간주되어 이벤트가 발생합니다. + * + * - 카메라가 애니메이션 없이 움직일 때. 단, 사용자가 제스처로 지도를 움직이는 경우 제스처가 완전히 끝날 때까지는 연속적인 이동으로 간주되어 이벤트가 발생하지 않습니다. + * - 카메라 애니메이션이 완료될 때. + * - NaverMap.cancelTransitions()가 호출되어 카메라 애니메이션이 명시적으로 취소될 때. + * @see {@link Camera} + * @see {@link Region} + * @event + */ + onCameraIdle?: (params: Camera & { region: Region }) => void; + /** * 맵을 클릭했을 때 발생하는 이벤트입니다. * @@ -605,6 +619,7 @@ export const NaverMapView = forwardRef( logoMargin, onCameraChanged: onCameraChangedProp, + onCameraIdle: onCameraIdleProp, onTapMap: onTapMapProp, onInitialized, onOptionChanged, @@ -741,6 +756,43 @@ export const NaverMapView = forwardRef( } ); + const onCameraIdle = useStableCallback( + ({ + nativeEvent: { + bearing, + latitude, + longitude, + tilt, + zoom, + regionLatitude, + regionLatitudeDelta, + regionLongitude, + regionLongitudeDelta, + }, + }: NativeSyntheticEvent< + Camera & { + regionLatitude: Double; + regionLongitude: Double; + regionLatitudeDelta: Double; + regionLongitudeDelta: Double; + } + >) => { + onCameraIdleProp?.({ + zoom, + tilt, + latitude, + longitude, + bearing, + region: { + latitude: regionLatitude, + longitude: regionLongitude, + latitudeDelta: regionLatitudeDelta, + longitudeDelta: regionLongitudeDelta, + }, + }); + } + ); + const onTapMap = useStableCallback( ({ nativeEvent: { longitude, latitude, x, y }, @@ -986,6 +1038,7 @@ export const NaverMapView = forwardRef( symbolPerspectiveRatio={clamp(symbolPerspectiveRatio, 0, 1)} onInitialized={onInitialized} onCameraChanged={onCameraChangedProp ? onCameraChanged : undefined} + onCameraIdle={onCameraIdleProp ? onCameraIdle : undefined} onTapMap={onTapMapProp ? onTapMap : undefined} onOptionChanged={onOptionChanged} mapPadding={mapPadding} diff --git a/src/spec/RNCNaverMapViewNativeComponent.ts b/src/spec/RNCNaverMapViewNativeComponent.ts index d99465f0..176b3cfe 100644 --- a/src/spec/RNCNaverMapViewNativeComponent.ts +++ b/src/spec/RNCNaverMapViewNativeComponent.ts @@ -162,6 +162,19 @@ interface Props extends ViewProps { regionLongitudeDelta: Double; }> >; + onCameraIdle?: DirectEventHandler< + Readonly<{ + latitude: Double; + longitude: Double; + zoom: Double; + tilt: Double; + bearing: Double; + regionLatitude: Double; + regionLongitude: Double; + regionLatitudeDelta: Double; + regionLongitudeDelta: Double; + }> + >; onTapMap?: DirectEventHandler< Readonly<{ latitude: Double;