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..02bc3fe2 --- /dev/null +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/event/NaverMapCameraIdleEvent.kt @@ -0,0 +1,34 @@ +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, +) : 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) + } + + 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 1ed6f1b4..d9f8e187 100644 --- a/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapView.kt +++ b/android/src/main/java/com/mjstudio/reactnativenavermap/mapview/RNCNaverMapView.kt @@ -6,6 +6,7 @@ import android.view.ViewGroup import com.airbnb.android.react.maps.ViewAttacherGroup 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 @@ -69,6 +70,21 @@ class RNCNaverMapView( } } + it.addOnCameraIdleListener { + reactContext.emitEvent(reactTag) { surfaceId, reactTag -> + NaverMapCameraIdleEvent( + surfaceId, + reactTag, + it.cameraPosition.target.latitude, + it.cameraPosition.target.longitude, + it.cameraPosition.zoom, + it.cameraPosition.tilt, + it.cameraPosition.bearing, + ) + } + } + + 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 d833adf6..8caae4d3 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.NaverMapCoordinateToScreenEvent import com.mjstudio.reactnativenavermap.event.NaverMapInitializeEvent import com.mjstudio.reactnativenavermap.event.NaverMapOptionChangeEvent @@ -114,6 +115,7 @@ class RNCNaverMapViewManager : RNCNaverMapViewManagerSpec 2.1.12) @@ -1187,7 +1100,6 @@ DEPENDENCIES: - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`) - - React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`) - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - "react-native-slider (from `../node_modules/@react-native-community/slider`)" @@ -1207,10 +1119,7 @@ DEPENDENCIES: - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`) - React-rncore (from `../node_modules/react-native/ReactCommon`) - - React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) - - React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`) - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) - - React-RuntimeHermes (from `../node_modules/react-native/ReactCommon/react/runtime`) - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) @@ -1232,6 +1141,8 @@ EXTERNAL SOURCES: :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" FBLazyVector: :path: "../node_modules/react-native/Libraries/FBLazyVector" + FBReactNativeSpec: + :path: "../node_modules/react-native/React/FBReactNativeSpec" glog: :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" hermes-engine: @@ -1277,8 +1188,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/jsiexecutor" React-jsinspector: :path: "../node_modules/react-native/ReactCommon/jsinspector-modern" - React-jsitracing: - :path: "../node_modules/react-native/ReactCommon/hermes/executor/" React-logger: :path: "../node_modules/react-native/ReactCommon/logger" React-Mapbuffer: @@ -1317,14 +1226,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/react/renderer/debug" React-rncore: :path: "../node_modules/react-native/ReactCommon" - React-RuntimeApple: - :path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios" - React-RuntimeCore: - :path: "../node_modules/react-native/ReactCommon/react/runtime" React-runtimeexecutor: :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" - React-RuntimeHermes: - :path: "../node_modules/react-native/ReactCommon/react/runtime" React-runtimescheduler: :path: "../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" React-utils: @@ -1340,19 +1243,20 @@ SPEC CHECKSUMS: boost: d3f49c53809116a5d38da093a8aa78bf551aed09 DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 FBLazyVector: f64d1e2ea739b4d8f7e4740cde18089cd97fe864 + FBReactNativeSpec: 9f2b8b243131565335437dba74923a8d3015e780 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 hermes-engine: 9cecf9953a681df7556b8cc9c74905de8f3293c0 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - mj-studio-react-native-naver-map: 375d2fd0ae64efde2ab131b24440dfeadd2c0959 - NMapsGeometry: 53c573ead66466681cf123f99f698dc8071a4b83 + mj-studio-react-native-naver-map: 0d846e0043bba0c75ee1e00af2006716990c7d26 + NMapsGeometry: 4e02554fa9880ef02ed96b075dc84355d6352479 NMapsMap: 36dc18a1f5f315121b33fccd2398c7a702bf0fc8 RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0 RCTRequired: ca1d7414aba0b27efcfa2ccd37637edb1ab77d96 RCTTypeSafety: 678e344fb976ff98343ca61dc62e151f3a042292 React: e296bcebb489deaad87326067204eb74145934ab React-callinvoker: d0b7015973fa6ccb592bb0363f6bc2164238ab8c - React-Codegen: df86ee5fa0498fb4d1c7e6ac324a58b7fdffbbaa + React-Codegen: f034a5de6f28e15e8d95d171df17e581d5309268 React-Core: 44c936d0ab879e9c32e5381bd7596a677c59c974 React-CoreModules: 558228e12cddb9ca00ff7937894cc5104a21be6b React-cxxreact: 1fcf565012c203655b3638f35aa03c13c2ed7e9e @@ -1366,18 +1270,17 @@ SPEC CHECKSUMS: React-jsi: ae102ccb38d2e4d0f512b7074d0c9b4e1851f402 React-jsiexecutor: bd12ec75873d3ef0a755c11f878f2c420430f5a9 React-jsinspector: 85583ef014ce53d731a98c66a0e24496f7a83066 - React-jsitracing: 4fed160d939e93a39049481f47744af246a7ac2c React-logger: 3eb80a977f0d9669468ef641a5e1fabbc50a09ec React-Mapbuffer: 84ea43c6c6232049135b1550b8c60b2faac19fab - react-native-slider: 331678c88bd66842da290aa5ce43083d74ba2884 + react-native-slider: 09e5a8b7e766d3b5ae24ec15c5c4ec2679ca0f8c React-nativeconfig: b4d4e9901d4cabb57be63053fd2aa6086eb3c85f React-NativeModulesApple: cd26e56d56350e123da0c1e3e4c76cb58a05e1ee React-perflogger: 5f49905de275bac07ac7ea7f575a70611fa988f2 React-RCTActionSheet: 37edf35aeb8e4f30e76c82aab61f12d1b75c04ec React-RCTAnimation: a69de7f3daa8462743094f4736c455e844ea63f7 - React-RCTAppDelegate: 5d3238045cfc5d6b157550e62c3cb6e2f7f2a5a6 + React-RCTAppDelegate: 51fb96b554a6acd0cd7818acecd5aa5ca2f3ab9f React-RCTBlob: d91771caebf2d015005d750cd1dc2b433ad07c99 - React-RCTFabric: 910a000f2470943ef39edc606f065fb61b138401 + React-RCTFabric: c5b9451d1f2b546119b7a0353226a8a26247d4a9 React-RCTImage: a0bfe87b6908c7b76bd7d74520f40660bd0ad881 React-RCTLinking: 5f10be1647952cceddfa1970fdb374087582fc34 React-RCTNetwork: a0bc3dd45a2dc7c879c80cebb6f9707b2c8bbed6 @@ -1385,18 +1288,15 @@ SPEC CHECKSUMS: React-RCTText: 4119d9e53ca5db9502b916e1b146e99798986d21 React-RCTVibration: 55bd7c48487eb9a2562f2bd3fdc833274f5b0636 React-rendererdebug: 5fa97ba664806cee4700e95aec42dff1b6f8ea36 - React-rncore: a3534bcdcf253f7ecc1f0ee36bfe8f4035ea1432 - React-RuntimeApple: c9886b8729f1e2fd5a551e54c617391d5172140e - React-RuntimeCore: 17e41e15c4933e0a127317e8ba0e582210a24fdc + React-rncore: b0a8e1d14dabb7115c7a5b4ec8b9b74d1727d382 React-runtimeexecutor: bb328dbe2865f3a550df0240df8e2d8c3aaa4c57 - React-RuntimeHermes: a4a1f5e24555292aa6a5f176fc41ad51878220d3 React-runtimescheduler: 9636eee762c699ca7c85751a359101797e4c8b3b React-utils: d16c1d2251c088ad817996621947d0ac8167b46c ReactCommon: 2aa35648354bd4c4665b9a5084a7d37097b89c10 - RNPermissions: 6fa25833c1a80f35c03e0a456ffe591b69f09e62 + RNPermissions: a4c964f030841f35d918ab3248df9dec9db5f44d SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 - Yoga: 805bf71192903b20fc14babe48080582fee65a80 + Yoga: d17d2cc8105eed528474683b42e2ea310e1daf61 PODFILE CHECKSUM: 84911b4c52757798c040fdc33fae23430223c20c -COCOAPODS: 1.14.3 +COCOAPODS: 1.16.2 diff --git a/example/src/App.tsx b/example/src/App.tsx index e8235fe8..22d5a4a6 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -341,6 +341,12 @@ export default function App() { // } onTapMap={(args) => console.log(`Map Tapped: ${formatJson(args)}`)} clusters={clusterers} + onCameraIdle={(args) => { + console.log('onCameraIdle', args); + }} + onCameraChanged={(args) => { + console.log('onCameraChanged', args); + }} > {renderOverlays()} @@ -361,7 +367,7 @@ export default function App() { onPress={() => setMapType( MapTypes[ - (MapTypes.findIndex((v) => v === mapType) + 1) % MapTypes.length + (MapTypes.findIndex((v) => v === mapType) + 1) % MapTypes.length ]! ) } diff --git a/ios/RNCNaverMapViewImpl.h b/ios/RNCNaverMapViewImpl.h index e59b8d34..76be058f 100644 --- a/ios/RNCNaverMapViewImpl.h +++ b/ios/RNCNaverMapViewImpl.h @@ -89,6 +89,7 @@ using namespace facebook::react; @property(nonatomic, copy) RCTDirectEventBlock onInitialized; @property(nonatomic, copy) RCTDirectEventBlock onOptionChanged; @property(nonatomic, copy) RCTDirectEventBlock onCameraChanged; +@property(nonatomic, copy) RCTDirectEventBlock onCameraIdle; @property(nonatomic, copy) RCTDirectEventBlock onTapMap; @property(nonatomic, copy) RCTDirectEventBlock onScreenToCoordinate; @property(nonatomic, copy) RCTDirectEventBlock onCoordinateToScreen; diff --git a/ios/RNCNaverMapViewImpl.mm b/ios/RNCNaverMapViewImpl.mm index 01590d4d..b7e9dc1d 100644 --- a/ios/RNCNaverMapViewImpl.mm +++ b/ios/RNCNaverMapViewImpl.mm @@ -443,6 +443,25 @@ - (void)mapView:(NMFMapView*)mapView cameraIsChangingByReason:(NSInteger)reason }); } +- (void)mapViewCameraIdle:(NMFMapView*)mapView { + if (!self.onCameraIdle) + return; + NMFCameraPosition* pos = mapView.cameraPosition; + NMGLatLngBounds* bounds = mapView.coveringBounds; + + self.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 (self.onTapMap) { self.onTapMap(@{ diff --git a/ios/RNCNaverMapViewManager.mm b/ios/RNCNaverMapViewManager.mm index 81a5ad42..8a4637e4 100644 --- a/ios/RNCNaverMapViewManager.mm +++ b/ios/RNCNaverMapViewManager.mm @@ -68,6 +68,7 @@ - (UIView*)view { 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 b6e6d29c..24946efd 100644 --- a/src/component/NaverMapView.tsx +++ b/src/component/NaverMapView.tsx @@ -34,6 +34,7 @@ import type { CameraMoveBaseParams } from '../types/CameraMoveBaseParams'; import type { CameraAnimationEasing } from '../types/CameraAnimationEasing'; import type { ClusterMarkerProp } from '../types/ClusterMarkerProp'; import hash from 'object-hash'; +import type { Double } from 'react-native/Libraries/Types/CodegenTypes'; /** * @category Hell @@ -423,6 +424,20 @@ export interface NaverMapViewProps extends ViewProps { } ) => void; + /** + * 카메라의 움직임이 끝나 대기 상태가 되면 카메라 대기 이벤트가 발생합니다. + * + * 카메라는 다음과 같은 시점에 대기 상태가 된 것으로 간주되어 이벤트가 발생합니다. + * + * - 카메라가 애니메이션 없이 움직일 때. 단, 사용자가 제스처로 지도를 움직이는 경우 제스처가 완전히 끝날 때까지는 연속적인 이동으로 간주되어 이벤트가 발생하지 않습니다. + * - 카메라 애니메이션이 완료될 때. + * - NaverMap.cancelTransitions()가 호출되어 카메라 애니메이션이 명시적으로 취소될 때. + * @see {@link Camera} + * @see {@link Region} + * @event + */ + onCameraIdle?: (params: Camera & { region: Region }) => void; + /** * 맵을 클릭했을 때 발생하는 이벤트입니다. * @@ -576,6 +591,7 @@ export const NaverMapView = forwardRef( logoMargin, onCameraChanged: onCameraChangedProp, + onCameraIdle: onCameraIdleProp, onTapMap: onTapMapProp, onInitialized, onOptionChanged, @@ -650,6 +666,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 }, @@ -895,6 +948,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 93512a1a..a1290e8d 100644 --- a/src/spec/RNCNaverMapViewNativeComponent.ts +++ b/src/spec/RNCNaverMapViewNativeComponent.ts @@ -139,6 +139,19 @@ interface Props extends ViewProps { reason: Int32 /* CameraChangeReason */; }> >; + 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;