diff --git a/index.js b/index.js index 04c58b5076..f40d678189 100644 --- a/index.js +++ b/index.js @@ -1 +1,4 @@ -// Placeholder file to fix Expo not resolving config plugin +export * from './src/Mapbox'; +import * as Mapbox from './src/Mapbox'; + +export default Mapbox; diff --git a/ios/RNMBX/Offline/RNMBXOfflineModule.m b/ios/RNMBX/Offline/RNMBXOfflineModule.m index 18083931f8..ec005429a1 100644 --- a/ios/RNMBX/Offline/RNMBXOfflineModule.m +++ b/ios/RNMBX/Offline/RNMBXOfflineModule.m @@ -1,4 +1,4 @@ -#import "React/RCTBridgeModule.h" +#import #import @interface RCT_EXTERN_MODULE(RNMBXOfflineModule, RCTEventEmitter) diff --git a/ios/RNMBX/Offline/RNMBXOfflineModule.swift b/ios/RNMBX/Offline/RNMBXOfflineModule.swift index e305343a74..f416de4799 100644 --- a/ios/RNMBX/Offline/RNMBXOfflineModule.swift +++ b/ios/RNMBX/Offline/RNMBXOfflineModule.swift @@ -18,7 +18,7 @@ class RNMBXOfflineModule: RCTEventEmitter { var hasListeners = false static let RNMapboxInfoMetadataKey = "_rnmapbox" - + enum Callbacks : String { case error = "MapboOfflineRegionError" case progress = "MapboxOfflineRegionProgress" @@ -33,21 +33,21 @@ class RNMBXOfflineModule: RCTEventEmitter { } lazy var offlineManager : OfflineManager = { - #if RNMBX_11 +#if RNMBX_11 return OfflineManager() - #else +#else return OfflineManager(resourceOptions: .init(accessToken: RNMBXModule.accessToken!)) - #endif +#endif }() lazy var offlineRegionManager: OfflineRegionManager = { - #if RNMBX_11 +#if RNMBX_11 return OfflineRegionManager() - #else +#else return OfflineRegionManager(resourceOptions: .init(accessToken: RNMBXModule.accessToken!)) - #endif +#endif }() - + lazy var tileStore : TileStore = { return TileStore.default }() @@ -58,7 +58,7 @@ class RNMBXOfflineModule: RCTEventEmitter { self.progress = progress self.metadata = metadata self.state = state - + if let rnMetadata = metadata[RNMapboxInfoMetadataKey] as? [String:Any] { if let styleURI = rnMetadata["styleURI"] as? String { self.styleURI = StyleURI(rawValue: styleURI) @@ -101,13 +101,13 @@ class RNMBXOfflineModule: RCTEventEmitter { ] self.metadata = metadata } - + var name: String var cancelable: Cancelable? = nil var progress : TileRegionLoadProgress? = nil var state : State = .inactive var metadata : [String:Any] - + // Stored in metadata for resume functionality: var bounds: Geometry? = nil var zoomRange: ClosedRange? = nil @@ -149,7 +149,7 @@ class RNMBXOfflineModule: RCTEventEmitter { public func constantsToExport() -> [AnyHashable: Any]! { return [:] } - + @objc override public func supportedEvents() -> [String] { @@ -295,7 +295,7 @@ class RNMBXOfflineModule: RCTEventEmitter { reject("migrateOfflineCache", error.localizedDescription, error) } } - + @objc func resetDatabase(_ resolve: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) { self.tileStore.allTileRegions { result in @@ -358,20 +358,20 @@ class RNMBXOfflineModule: RCTEventEmitter { let stylePackLoadOptions = StylePackLoadOptions(glyphsRasterizationMode: .ideographsRasterizedLocally, metadata: pack.metadata) - #if RNMBX_11 +#if RNMBX_11 let descriptorOptions = TilesetDescriptorOptions( styleURI: styleURI, zoomRange: zoomRange, tilesets: [], // RNMBX_11_TODO stylePackOptions: stylePackLoadOptions ) - #else +#else let descriptorOptions = TilesetDescriptorOptions( styleURI: styleURI, zoomRange: zoomRange, stylePackOptions: stylePackLoadOptions ) - #endif +#endif let tilesetDescriptor = self.offlineManager.createTilesetDescriptor(for: descriptorOptions) let loadOptions = TileRegionLoadOptions( @@ -491,7 +491,7 @@ class RNMBXOfflineModule: RCTEventEmitter { ] let completed = (region.completedResourceCount == region.requiredResourceCount) - + var metadata : [String:Any] = metadata ?? [:] metadata["name"] = region.id @@ -503,7 +503,7 @@ class RNMBXOfflineModule: RCTEventEmitter { "metadata": String(data:try! JSONSerialization.data(withJSONObject: metadata, options: [.prettyPrinted]), encoding: .utf8), "bounds": jsonBounds ] - + if region.requiredResourceCount > 0 { result["percentage"] = region.toPercentage() } else { @@ -512,12 +512,12 @@ class RNMBXOfflineModule: RCTEventEmitter { if let expires = region.expires { result["expires"] = expires.toJSONString() } - + return result } return [:] } - + func toProgress(region: TileRegion) -> TileRegionLoadProgress? { return TileRegionLoadProgress(completedResourceCount: region.completedResourceCount, completedResourceSize: region.completedResourceSize, erroredResourceCount: 0, requiredResourceCount: region.requiredResourceCount, loadedResourceCount: 0, loadedResourceSize: 0) } @@ -554,7 +554,7 @@ class RNMBXOfflineModule: RCTEventEmitter { } self.sendEvent(withName: name, body: event.toJSON()) } - + func _makeRegionStatusPayload(_ name:String, progress: TileRegionLoadProgress?, state: State, metadata:[String:Any]?) -> [String:Any?] { var result : [String:Any?] = [:] if let progress = progress { @@ -610,7 +610,7 @@ extension RNMBXOfflineModule { progressEventThrottle.waitBetweenEvents = throttleValue.doubleValue } - + func shouldSendProgressEvent(progress: TileRegionLoadProgress, state: State) -> Bool { let currentTimestamp: Double = CACurrentMediaTime() * 1000.0 @@ -631,7 +631,7 @@ extension RNMBXOfflineModule { progressEventThrottle.lastSentTimestamp = currentTimestamp return true; } - + return false; } } diff --git a/ios/RNMBX/Offline/RNMBXOfflineModuleLegacy.m b/ios/RNMBX/Offline/RNMBXOfflineModuleLegacy.m index 550f67b058..76da02d4f5 100644 --- a/ios/RNMBX/Offline/RNMBXOfflineModuleLegacy.m +++ b/ios/RNMBX/Offline/RNMBXOfflineModuleLegacy.m @@ -1,4 +1,4 @@ -#import "React/RCTBridgeModule.h" +#import #import @interface RCT_EXTERN_MODULE(RNMBXOfflineModuleLegacy, RCTEventEmitter) diff --git a/ios/RNMBX/Offline/RNMBXTileStoreModule.m b/ios/RNMBX/Offline/RNMBXTileStoreModule.m index a98e102a95..e43be8fbdc 100644 --- a/ios/RNMBX/Offline/RNMBXTileStoreModule.m +++ b/ios/RNMBX/Offline/RNMBXTileStoreModule.m @@ -1,4 +1,4 @@ -#import "React/RCTBridgeModule.h" +#import #import @interface RCT_EXTERN_MODULE(RNMBXTileStoreModule, NSObject) diff --git a/ios/RNMBX/RNMBXAtmosphere.swift b/ios/RNMBX/RNMBXAtmosphere.swift index e423da2ab0..f8bed1341f 100644 --- a/ios/RNMBX/RNMBXAtmosphere.swift +++ b/ios/RNMBX/RNMBXAtmosphere.swift @@ -24,12 +24,11 @@ public class RNMBXAtmosphere : RNMBXSingletonLayer, RNMBXMapComponent, RNMBXSour public func removeFromMap(_ map: RNMBXMapView, reason _: RemovalReason) -> Bool { self.map = nil - guard let mapboxMap = map.mapboxMap else { - return false + map.withMapboxMap { [weak self] _mapboxMap in + guard let self = self else { return } + let style = _mapboxMap.style + self.removeFromMap(map, style: style) } - - let style = mapboxMap.style - removeFromMap(map, style: style) return true } diff --git a/ios/RNMBX/RNMBXCalloutViewManager.m b/ios/RNMBX/RNMBXCalloutViewManager.m index 62205d5b75..1db2ac4261 100644 --- a/ios/RNMBX/RNMBXCalloutViewManager.m +++ b/ios/RNMBX/RNMBXCalloutViewManager.m @@ -1,4 +1,4 @@ -#import "React/RCTBridgeModule.h" +#import #import #import diff --git a/ios/RNMBX/RNMBXCamera.swift b/ios/RNMBX/RNMBXCamera.swift index 69c7e2777a..4a76407ee3 100644 --- a/ios/RNMBX/RNMBXCamera.swift +++ b/ios/RNMBX/RNMBXCamera.swift @@ -15,7 +15,7 @@ public enum RemovalReason { case ViewRemoval, StyleChange, OnDestroy, ComponentChange, Reorder } -public protocol RNMBXMapComponent: AnyObject { +public protocol RNMBXMapComponent: AnyObject, CustomStringConvertible { func addToMap(_ map: RNMBXMapView, style: Style) func removeFromMap(_ map: RNMBXMapView, reason: RemovalReason) -> Bool @@ -40,20 +40,22 @@ struct CameraUpdateItem { var duration: TimeInterval? func execute(map: RNMBXMapView, cameraAnimator: inout BasicCameraAnimator?) { - logged("CameraUpdateItem.execute") { - if let center = camera.center { - try center.validate() - } + map.withMapView { _mapView in + logged("CameraUpdateItem.execute") { + if let center = camera.center { + try center.validate() + } - switch mode { - case .flight: - map.mapView.camera.fly(to: camera, duration: duration) - case .ease: - map.mapView.camera.ease(to: camera, duration: duration ?? 0, curve: .easeInOut, completion: nil) - case .linear: - map.mapView.camera.ease(to: camera, duration: duration ?? 0, curve: .linear, completion: nil) - default: - map.mapboxMap.setCamera(to: camera) + switch mode { + case .flight: + _mapView.camera.fly(to: camera, duration: duration) + case .ease: + _mapView.camera.ease(to: camera, duration: duration ?? 0, curve: .easeInOut, completion: nil) + case .linear: + _mapView.camera.ease(to: camera, duration: duration ?? 0, curve: .linear, completion: nil) + default: + _mapView.mapboxMap.setCamera(to: camera) + } } } } @@ -92,7 +94,9 @@ open class RNMBXMapComponentBase : UIView, RNMBXMapComponent { func withMapView(_ callback: @escaping (_ mapView: MapView) -> Void) { withRNMBXMapView { mapView in - callback(mapView.mapView) + mapView.withMapView { _mapView in + callback(_mapView) + } } } @@ -529,7 +533,9 @@ open class RNMBXCamera : RNMBXMapComponentBase { return false } - map.mapView.viewport.removeStatusObserver(self) + map.withMapView { _mapView in + _mapView.viewport.removeStatusObserver(self) + } return super.removeFromMap(map, reason:reason) } diff --git a/ios/RNMBX/RNMBXCameraViewManager.m b/ios/RNMBX/RNMBXCameraViewManager.m index 70027938e4..75eba85d34 100644 --- a/ios/RNMBX/RNMBXCameraViewManager.m +++ b/ios/RNMBX/RNMBXCameraViewManager.m @@ -1,6 +1,6 @@ #if !RCT_NEW_ARCH_ENABLED -#import "React/RCTBridgeModule.h" +#import #import #import diff --git a/ios/RNMBX/RNMBXCustomLocationProvider.swift b/ios/RNMBX/RNMBXCustomLocationProvider.swift index 8ff3c5480b..0bd7ed6a19 100644 --- a/ios/RNMBX/RNMBXCustomLocationProvider.swift +++ b/ios/RNMBX/RNMBXCustomLocationProvider.swift @@ -47,8 +47,8 @@ public class RNMBXCustomLocationProvider: UIView, RNMBXMapComponent { public func addToMap(_ map: RNMBXMapView, style: Style) { self.map = map - if let mapView = map.mapView { - installCustomeLocationProviderIfNeeded(mapView: mapView) + map.withMapView{ _mapView in + self.installCustomeLocationProviderIfNeeded(mapView: _mapView) } } @@ -61,10 +61,10 @@ public class RNMBXCustomLocationProvider: UIView, RNMBXMapComponent { } public func removeFromMap(_ map: RNMBXMapView, reason: RemovalReason) -> Bool { - if let mapView = map.mapView { - removeCustomLocationProvider(mapView: mapView) + map.withMapView { _mapView in + self.removeCustomLocationProvider(mapView: _mapView) + self.map = nil } - self.map = nil return true } diff --git a/ios/RNMBX/RNMBXImageSource.swift b/ios/RNMBX/RNMBXImageSource.swift index b60dee6234..86028ba419 100644 --- a/ios/RNMBX/RNMBXImageSource.swift +++ b/ios/RNMBX/RNMBXImageSource.swift @@ -7,8 +7,11 @@ public class RNMBXImageSource : RNMBXSource { didSet { if var source = source as? ImageSource { source.url = url - self.doUpdate { (style) in - try! style.setSourceProperty(for: id, property: "url", value: url) + self.map?.withMapboxMap { [weak self] _mapboxMap in + guard let self = self else { return } + self.doUpdate(_mapboxMap: _mapboxMap) { (style) in + try! style.setSourceProperty(for: self.id, property: "url", value: source.url) + } } } } @@ -22,8 +25,11 @@ public class RNMBXImageSource : RNMBXSource { } else { source.coordinates = nil } - self.doUpdate { (style) in - try! style.setSourceProperty(for: id, property: "coordinates", value: source.coordinates) + self.map?.withMapboxMap { [weak self] _mapboxMap in + guard let self = self else { return } + self.doUpdate(_mapboxMap: _mapboxMap) { (style) in + try! style.setSourceProperty(for: self.id, property: "coordinates", value: source.coordinates) + } } } } @@ -51,14 +57,14 @@ public class RNMBXImageSource : RNMBXSource { return result } - func doUpdate(_ update:(Style) -> Void) { + func doUpdate(_mapboxMap: MapboxMap, _ update:(Style) -> Void) { guard let map = self.map, let _ = self.source, - map.mapboxMap.style.sourceExists(withId: id) else { + _mapboxMap.style.sourceExists(withId: id) else { return } - let style = map.mapboxMap.style + let style = _mapboxMap.style update(style) } diff --git a/ios/RNMBX/RNMBXInteractiveElement.swift b/ios/RNMBX/RNMBXInteractiveElement.swift index 88b9a83f69..362a557871 100644 --- a/ios/RNMBX/RNMBXInteractiveElement.swift +++ b/ios/RNMBX/RNMBXInteractiveElement.swift @@ -24,7 +24,12 @@ public class RNMBXInteractiveElement : UIView, RNMBXMapComponent { } didSet { if oldValue != nil && oldValue != id { - if let map = map { addToMap(map, style: map.mapboxMap.style) } + if let map = map { + self.map?.withMapboxMap { [weak self] _mapboxMap in + guard let self = self else { return } + self.addToMap(map, style: _mapboxMap.style) + } + } } } } diff --git a/ios/RNMBX/RNMBXLayer.swift b/ios/RNMBX/RNMBXLayer.swift index de37d2d670..e0487d1a35 100644 --- a/ios/RNMBX/RNMBXLayer.swift +++ b/ios/RNMBX/RNMBXLayer.swift @@ -322,7 +322,10 @@ public class RNMBXLayer : UIView, RNMBXMapComponent, RNMBXSourceConsumer { } public func removeFromMap(_ map: RNMBXMapView, reason: RemovalReason) -> Bool { - removeFromMap(map.mapboxMap.style) + map.withMapboxMap { [weak self] _mapboxMap in + guard let self = self else { return } + self.removeFromMap(_mapboxMap.style) + } return true } diff --git a/ios/RNMBX/RNMBXLight.swift b/ios/RNMBX/RNMBXLight.swift index aab3f9f64c..fe70facd89 100644 --- a/ios/RNMBX/RNMBXLight.swift +++ b/ios/RNMBX/RNMBXLight.swift @@ -56,9 +56,12 @@ public class RNMBXLight: UIView, RNMBXMapComponent { } public func addToMap(_ map: RNMBXMapView, style: Style) { - self.map = map.mapboxMap - if (reactStyle != nil) { - addStyles() + map.withMapboxMap { [weak self] _mapboxMap in + guard let self = self else { return } + self.map = _mapboxMap + if (self.reactStyle != nil) { + self.addStyles() + } } } diff --git a/ios/RNMBX/RNMBXLocationModule.m b/ios/RNMBX/RNMBXLocationModule.m index 8b89774af3..9f85c35583 100644 --- a/ios/RNMBX/RNMBXLocationModule.m +++ b/ios/RNMBX/RNMBXLocationModule.m @@ -1,4 +1,4 @@ -#import "React/RCTBridgeModule.h" +#import #import @class RNMBXLocation; diff --git a/ios/RNMBX/RNMBXLogging.m b/ios/RNMBX/RNMBXLogging.m index d7c05def0f..f680b86f18 100644 --- a/ios/RNMBX/RNMBXLogging.m +++ b/ios/RNMBX/RNMBXLogging.m @@ -1,4 +1,4 @@ -#import "React/RCTBridgeModule.h" +#import @interface RCT_EXTERN_MODULE(RNMBXLogging, NSObject) diff --git a/ios/RNMBX/RNMBXMapView.swift b/ios/RNMBX/RNMBXMapView.swift index 5f07618d0e..dcc69b6a39 100644 --- a/ios/RNMBX/RNMBXMapView.swift +++ b/ios/RNMBX/RNMBXMapView.swift @@ -212,17 +212,28 @@ open class RNMBXMapView: UIView, RCTInvalidating { #if RNMBX_11 var cancelables = Set() #endif - - lazy var pointAnnotationManager : RNMBXPointAnnotationManager = { - let result = RNMBXPointAnnotationManager(annotations: mapView.annotations, mapView: mapView) + + private var _pointAnnotationManager : RNMBXPointAnnotationManager? = nil + func getPointAnnotationManager(_mapView: MapView) -> RNMBXPointAnnotationManager { + if let pointAnnotationManager = _pointAnnotationManager { + return pointAnnotationManager + } + let result = RNMBXPointAnnotationManager(annotations: _mapView.annotations, mapView: _mapView) self._removeMapboxLongPressGestureRecognizer() + _pointAnnotationManager = result return result - }() - - lazy var calloutAnnotationManager : MapboxMaps.PointAnnotationManager = { - return mapView.annotations.makePointAnnotationManager(id: "RNMBX-mapview-callouts") - }() - + } + + private var _calloutAnnotationManager : MapboxMaps.PointAnnotationManager? = nil + func getCalloutAnnotationManager(_mapView: MapView) -> MapboxMaps.PointAnnotationManager { + if let calloutAnnotationManager = _calloutAnnotationManager { + return calloutAnnotationManager + } + let result = _mapView.annotations.makePointAnnotationManager(id: "RNMBX-mapview-callouts") + _calloutAnnotationManager = result + return result + } + var _mapView: MapView! = nil func createMapView() -> MapView { if let mapViewImpl = mapViewImpl, let mapViewInstance = createAndAddMapViewImpl(mapViewImpl, self) { @@ -257,16 +268,6 @@ open class RNMBXMapView: UIView, RCTInvalidating { } } - @available(*, deprecated, renamed: "withMapView", message: "mapView can be nil if the map initialization has not finished, use withMapView instead") - public var mapView : MapView! { - get { return _mapView } - } - - @available(*, deprecated, renamed: "withMapboxMap", message: "mapboxMap can be nil if the map initialization has not finished, use withMapboxMap instead") - var mapboxMap: MapboxMap! { - get { _mapView?.mapboxMap } - } - @objc public func addToMap(_ subview: UIView) { withMapView { mapView in if let mapComponent = subview as? RNMBXMapComponent { @@ -338,11 +339,14 @@ open class RNMBXMapView: UIView, RCTInvalidating { } func waitForLayerWithID(_ layerId: String, _ callback: @escaping (_ layerId: String) -> Void) { - let style = mapView.mapboxMap.style; - if style.layerExists(withId: layerId) { - callback(layerId) - } else { - layerWaiters[layerId, default: []].append(callback) + withMapView { [weak self] _mapView in + guard let self = self else { return } + let style = _mapView.mapboxMap.style; + if style.layerExists(withId: layerId) { + callback(layerId) + } else { + self.layerWaiters[layerId, default: []].append(callback) + } } } @@ -449,9 +453,12 @@ open class RNMBXMapView: UIView, RCTInvalidating { } func applyProjection() { - logged("RNMBXMapView.setReactProjection") { - if let projection = projection { - try self.mapboxMap.style.setProjection(projection) + withMapboxMap { [weak self] _mapboxMap in + guard let self = self else { return } + if let projection = self.projection { + logged("RNMBXMapView.setReactProjection") { + try _mapboxMap.style.setProjection(projection) + } } } } @@ -465,10 +472,12 @@ open class RNMBXMapView: UIView, RCTInvalidating { func applyPreferredFramesPerSecond() { if let value = preferredFramesPerSecond { - if #available(iOS 15.0, *) { - self.mapView.preferredFrameRateRange = CAFrameRateRange(minimum: 1, maximum: Float(value)) - } else { - self.mapView.preferredFramesPerSecond = value + withMapView { _mapView in + if #available(iOS 15.0, *) { + _mapView.preferredFrameRateRange = CAFrameRateRange(minimum: 1, maximum: Float(value)) + } else { + _mapView.preferredFramesPerSecond = value + } } } } @@ -487,9 +496,12 @@ open class RNMBXMapView: UIView, RCTInvalidating { func applyLocalizeLabels() { onMapStyleLoaded { _ in - logged("RNMBXMapView.\(#function)") { - if let locale = self.locale { - try self.mapboxMap.style.localizeLabels(into: locale.locale, forLayerIds: locale.layerIds) + self.withMapboxMap { [weak self] _mapboxMap in + guard let self = self else { return } + logged("RNMBXMapView.\(#function)") { + if let locale = self.locale { + try _mapboxMap.style.localizeLabels(into: locale.locale, forLayerIds: locale.layerIds) + } } } } @@ -568,51 +580,54 @@ open class RNMBXMapView: UIView, RCTInvalidating { } func applyGestureSettings() { - if let gestures = self.mapView?.gestures { - var options = gestures.options - let settings = gestureSettings - if let doubleTapToZoomInEnabled = settings.doubleTapToZoomInEnabled as? Bool { - options.doubleTapToZoomInEnabled = doubleTapToZoomInEnabled - } - if let doubleTouchToZoomOutEnabled = settings.doubleTouchToZoomOutEnabled as? Bool { - options.doubleTouchToZoomOutEnabled = doubleTouchToZoomOutEnabled - } - if let pinchPanEnabled = settings.pinchPanEnabled as? Bool { - options.pinchPanEnabled = pinchPanEnabled - } - if let pinchZoomEnabled = settings.pinchZoomEnabled as? Bool { - options.pinchZoomEnabled = pinchZoomEnabled - } - if let pitchEnabled = settings.pitchEnabled as? Bool { - options.pitchEnabled = pitchEnabled - } - if let quickZoomEnabled = settings.quickZoomEnabled as? Bool { - options.quickZoomEnabled = quickZoomEnabled - } - if let rotateEnabled = settings.rotateEnabled as? Bool { - options.rotateEnabled = rotateEnabled - } - /* android only - if let rotateDecelerationEnabled = value["rotateDecelerationEnabled"] as? NSNumber { - options.rotateDecelerationEnabled = rotateDecelerationEnabled.boolValue - }*/ - if let panEnabled = settings.panEnabled as? Bool { - options.panEnabled = panEnabled - } - if let panDecelerationFactor = settings.panDecelerationFactor as? CGFloat { - options.panDecelerationFactor = panDecelerationFactor - } -#if RNMBX_11 - if let simultaneousRotateAndPinchZoomEnabled = settings.simultaneousRotateAndPinchZoomEnabled as? Bool { - options.simultaneousRotateAndPinchZoomEnabled = simultaneousRotateAndPinchZoomEnabled - } -#endif - /* android only - if let zoomAnimationAmount = value["zoomAnimationAmount"] as? NSNumber { - options.zoomAnimationAmount = zoomAnimationAmount.CGFloat - }*/ - if options != gestures.options { - gestures.options = options + withMapView { [weak self] _mapView in + guard let self = self else { return } + if let gestures = _mapView.gestures { + var options = gestures.options + let settings = self.gestureSettings + if let doubleTapToZoomInEnabled = settings.doubleTapToZoomInEnabled as? Bool { + options.doubleTapToZoomInEnabled = doubleTapToZoomInEnabled + } + if let doubleTouchToZoomOutEnabled = settings.doubleTouchToZoomOutEnabled as? Bool { + options.doubleTouchToZoomOutEnabled = doubleTouchToZoomOutEnabled + } + if let pinchPanEnabled = settings.pinchPanEnabled as? Bool { + options.pinchPanEnabled = pinchPanEnabled + } + if let pinchZoomEnabled = settings.pinchZoomEnabled as? Bool { + options.pinchZoomEnabled = pinchZoomEnabled + } + if let pitchEnabled = settings.pitchEnabled as? Bool { + options.pitchEnabled = pitchEnabled + } + if let quickZoomEnabled = settings.quickZoomEnabled as? Bool { + options.quickZoomEnabled = quickZoomEnabled + } + if let rotateEnabled = settings.rotateEnabled as? Bool { + options.rotateEnabled = rotateEnabled + } + /* android only + if let rotateDecelerationEnabled = value["rotateDecelerationEnabled"] as? NSNumber { + options.rotateDecelerationEnabled = rotateDecelerationEnabled.boolValue + }*/ + if let panEnabled = settings.panEnabled as? Bool { + options.panEnabled = panEnabled + } + if let panDecelerationFactor = settings.panDecelerationFactor as? CGFloat { + options.panDecelerationFactor = panDecelerationFactor + } + #if RNMBX_11 + if let simultaneousRotateAndPinchZoomEnabled = settings.simultaneousRotateAndPinchZoomEnabled as? Bool { + options.simultaneousRotateAndPinchZoomEnabled = simultaneousRotateAndPinchZoomEnabled + } + #endif + /* android only + if let zoomAnimationAmount = value["zoomAnimationAmount"] as? NSNumber { + options.zoomAnimationAmount = zoomAnimationAmount.CGFloat + }*/ + if options != gestures.options { + gestures.options = options + } } } } @@ -626,12 +641,15 @@ open class RNMBXMapView: UIView, RCTInvalidating { } func applyAttribution() { - if let visibility = attributionEnabled { - mapView.ornaments.options.attributionButton.visibility = visibility - } - if let options = attributionOptions { - mapView.ornaments.options.attributionButton.position = options.position - mapView.ornaments.options.attributionButton.margins = options.margins + withMapView { [weak self] _mapView in + guard let self = self else { return } + if let visibility = self.attributionEnabled { + _mapView.ornaments.options.attributionButton.visibility = visibility + } + if let options = self.attributionOptions { + _mapView.ornaments.options.attributionButton.position = options.position + _mapView.ornaments.options.attributionButton.margins = options.margins + } } } @@ -654,12 +672,15 @@ open class RNMBXMapView: UIView, RCTInvalidating { } func applyLogo() { - if let visibility = logoEnabled { - mapView.ornaments.options.logo.visibility = visibility - } - if let options = logoOptions { - mapView.ornaments.options.logo.position = options.position - mapView.ornaments.options.logo.margins = options.margins + withMapView { [weak self] _mapView in + guard let self = self else { return } + if let visibility = self.logoEnabled { + _mapView.ornaments.options.logo.visibility = visibility + } + if let options = self.logoOptions { + _mapView.ornaments.options.logo.position = options.position + _mapView.ornaments.options.logo.margins = options.margins + } } } @@ -703,28 +724,31 @@ open class RNMBXMapView: UIView, RCTInvalidating { } func applyCompass() { - var visibility: OrnamentVisibility = .hidden - if compassEnabled { - visibility = compassFadeWhenNorth ? .adaptive : .visible - } - mapView.ornaments.options.compass.visibility = visibility - - if let position = compassPosition { - mapView.ornaments.options.compass.position = position - } - if let margina = compassMargins { - mapView.ornaments.options.compass.margins = margina - } - - if let compassImage = compassImage { - onMapStyleLoaded { map in - let img = map.style.image(withId: compassImage) - self.mapView.ornaments.options.compass.image = img + withMapView { [weak self] _mapView in + guard let self = self else { return } + var visibility: OrnamentVisibility = .hidden + if self.compassEnabled { + visibility = self.compassFadeWhenNorth ? .adaptive : .visible + } + _mapView.ornaments.options.compass.visibility = visibility + + if let position = self.compassPosition { + _mapView.ornaments.options.compass.position = position + } + if let margina = self.compassMargins { + _mapView.ornaments.options.compass.margins = margina + } + + if let compassImage = self.compassImage { + self.onMapStyleLoaded { map in + let img = map.style.image(withId: compassImage) + _mapView.ornaments.options.compass.image = img + } + } else { + // Does not currently reset the image to the default. + // See https://github.com/mapbox/mapbox-maps-ios/issues/1673. + _mapView.ornaments.options.compass.image = nil } - } else { - // Does not currently reset the image to the default. - // See https://github.com/mapbox/mapbox-maps-ios/issues/1673. - self.mapView.ornaments.options.compass.image = nil } } @@ -768,14 +792,17 @@ open class RNMBXMapView: UIView, RCTInvalidating { } func applyScaleBar() { - if let enabled = scaleBarEnabled { - mapView.ornaments.options.scaleBar.visibility = enabled ? .visible : .hidden - } - if let position = scaleBarPosition { - mapView.ornaments.options.scaleBar.position = position - } - if let margins = scaleBarMargins { - mapView.ornaments.options.scaleBar.margins = margins + withMapView { [weak self] _mapView in + guard let self = self else { return } + if let enabled = self.scaleBarEnabled { + _mapView.ornaments.options.scaleBar.visibility = enabled ? .visible : .hidden + } + if let position = self.scaleBarPosition { + _mapView.ornaments.options.scaleBar.position = position + } + if let margins = self.scaleBarMargins { + _mapView.ornaments.options.scaleBar.margins = margins + } } } @@ -795,10 +822,13 @@ open class RNMBXMapView: UIView, RCTInvalidating { } func applyZoomEnabled() { - if let value = zoomEnabled { - self.mapView.gestures.options.quickZoomEnabled = value - self.mapView.gestures.options.doubleTapToZoomInEnabled = value - self.mapView.gestures.options.pinchZoomEnabled = value + withMapView { [weak self] _mapView in + guard let self = self else { return } + if let value = self.zoomEnabled { + _mapView.gestures.options.quickZoomEnabled = value + _mapView.gestures.options.doubleTapToZoomInEnabled = value + _mapView.gestures.options.pinchZoomEnabled = value + } } } @@ -809,9 +839,12 @@ open class RNMBXMapView: UIView, RCTInvalidating { } func applyScrollEnabled() { - if let value = scrollEnabled { - self.mapView.gestures.options.panEnabled = value - self.mapView.gestures.options.pinchPanEnabled = value + withMapView { [weak self] _mapView in + guard let self = self else { return } + if let value = self.scrollEnabled { + _mapView.gestures.options.panEnabled = value + _mapView.gestures.options.pinchPanEnabled = value + } } } @@ -822,8 +855,11 @@ open class RNMBXMapView: UIView, RCTInvalidating { } func applyRotateEnabled() { - if let value = rotateEnabled { - self.mapView.gestures.options.rotateEnabled = value + withMapView { [weak self] _mapView in + guard let self = self else { return } + if let value = self.rotateEnabled { + _mapView.gestures.options.rotateEnabled = value + } } } @@ -834,8 +870,11 @@ open class RNMBXMapView: UIView, RCTInvalidating { changed(.pitchEnabled) } func applyPitchEnabled() { - if let value = pitchEnabled { - self.mapView.gestures.options.pitchEnabled = value + withMapView { [weak self] _mapView in + guard let self = self else { return } + if let value = self.pitchEnabled { + _mapView.gestures.options.pitchEnabled = value + } } } @@ -872,31 +911,34 @@ open class RNMBXMapView: UIView, RCTInvalidating { } public func applyStyleURL() { - var initialLoad = !self.styleLoadWaiters.hasInited() - if !initialLoad { refreshComponentsBeforeStyleChange() } - if let value = reactStyleURL { - self.styleLoadWaiters.reset() - - if let _ = URL(string: value) { - if let styleURI = StyleURI(rawValue: value) { - mapView.mapboxMap.loadStyleURI(styleURI) - } else { - let event = RNMBXEvent(type:.mapLoadingError, payload: ["error": "invalid URI: \(value)"]); - self.fireEvent(event: event, callback: self.reactOnMapChange) + withMapView { [weak self] _mapView in + guard let self = self else { return } + var initialLoad = !self.styleLoadWaiters.hasInited() + if !initialLoad { self.refreshComponentsBeforeStyleChange() } + if let value = self.reactStyleURL { + self.styleLoadWaiters.reset() + + if let _ = URL(string: value) { + if let styleURI = StyleURI(rawValue: value) { + _mapView.mapboxMap.loadStyleURI(styleURI) + } else { + let event = RNMBXEvent(type:.mapLoadingError, payload: ["error": "invalid URI: \(value)"]); + self.fireEvent(event: event, callback: self.reactOnMapChange) + } + } else { + if RCTJSONParse(value, nil) != nil { + _mapView.mapboxMap.loadStyleJSON(value) } - } else { - if RCTJSONParse(value, nil) != nil { - mapView.mapboxMap.loadStyleJSON(value) } - } - if !initialLoad { - self.onNext(event: .styleLoaded) {_,_ in - self.addFeaturesToMap(style: self.mapboxMap.style) + if !initialLoad { + self.onNext(event: .styleLoaded) {_,_ in + self.addFeaturesToMap(style: _mapView.mapboxMap.style) + } } } + let event = RNMBXEvent(type:.willStartLoadingMap, payload: nil); + self.fireEvent(event: event, callback: self.reactOnMapChange) } - let event = RNMBXEvent(type:.willStartLoadingMap, payload: nil); - self.fireEvent(event: event, callback: self.reactOnMapChange) } private func getOrnamentOptionsFromPosition(_ position: [String: NSNumber]) -> (position: OrnamentPosition, margins: CGPoint)? { @@ -919,9 +961,11 @@ open class RNMBXMapView: UIView, RCTInvalidating { } func _removeMapboxLongPressGestureRecognizer() { - mapView.gestureRecognizers?.forEach { recognizer in - if (String(describing: type(of:recognizer)) == "MapboxLongPressGestureRecognizer") { - mapView.removeGestureRecognizer(recognizer) + withMapView { _mapView in + _mapView.gestureRecognizers?.forEach { recognizer in + if (String(describing: type(of:recognizer)) == "MapboxLongPressGestureRecognizer") { + _mapView.removeGestureRecognizer(recognizer) + } } } } @@ -935,40 +979,50 @@ open class RNMBXMapView: UIView, RCTInvalidating { extension RNMBXMapView { #if RNMBX_11 private func onEvery(event: MapEventType, handler: @escaping (RNMBXMapView, T) -> Void) { - let signal = event.method(self.mapView.mapboxMap) - signal.observe { [weak self] (mapEvent) in - guard let self = self else { return } + withMapView { _mapView in + let signal = event.method(_mapView.mapboxMap) + signal.observe { [weak self] (mapEvent) in + guard let self = self else { return } - handler(self, mapEvent) - }.store(in: &cancelables) + handler(self, mapEvent) + }.store(in: &cancelables) + } } private func onNext(event: MapEventType, handler: @escaping (RNMBXMapView, T) -> Void) { - let signal = event.method(self.mapView.mapboxMap) - signal.observeNext { [weak self] (mapEvent) in - guard let self = self else { return } + withMapView { _mapView in + let signal = event.method(_mapView.mapboxMap) + signal.observeNext { [weak self] (mapEvent) in + guard let self = self else { return } - handler(self, mapEvent) - }.store(in: &cancelables) + handler(self, mapEvent) + }.store(in: &cancelables) + } } #else private func onEvery(event: MapEvents.Event, handler: @escaping (RNMBXMapView, MapEvent) -> Void) { - let eventListener = self.mapView.mapboxMap.onEvery(event: event) { [weak self](mapEvent) in + withMapView { [weak self] _mapView in guard let self = self else { return } + let eventListener = _mapView.mapboxMap.onEvery(event: event) { [weak self](mapEvent) in + guard let self = self else { return } - handler(self, mapEvent) - } - eventListeners.append(eventListener) - if eventListeners.count > 20 { - Logger.log(level:.warn, message: "RNMBXMapView.onEvery, too much handler installed"); + handler(self, mapEvent) + } + self.eventListeners.append(eventListener) + if self.eventListeners.count > 20 { + Logger.log(level:.warn, message: "RNMBXMapView.onEvery, too much handler installed"); + } } } private func onNext(event: MapEvents.Event, handler: @escaping (RNMBXMapView, MapEvent) -> Void) { - self.mapView.mapboxMap.onNext(event: event) { [weak self](mapEvent) in + withMapView { [weak self] _mapView in guard let self = self else { return } + _mapView.mapboxMap.onNext(event: event) { [weak self](mapEvent) in + guard let self = self else { return } - handler(self, mapEvent) + handler(self, mapEvent) + } } } #endif @@ -979,28 +1033,31 @@ extension RNMBXMapView { } func applyOnMapChange() { - self.onEvery(event: .cameraChanged, handler: { (self, cameraEvent) in - self.wasGestureActive = self.isGestureActive - if self.handleMapChangedEvents.contains(.regionIsChanging) { - let event = RNMBXEvent(type:.regionIsChanging, payload: self.buildRegionObject()) - self.fireEvent(event: event, callback: self.reactOnMapChange) - } else if self.handleMapChangedEvents.contains(.cameraChanged) { - let event = RNMBXCameraChanged(type:.cameraChanged, payload: self.buildStateObject(), reactTag: self.reactTag) - self.eventDispatcher.send(event) - } - }) - - self.onEvery(event: .mapIdle, handler: { (self, cameraEvent) in - if self.handleMapChangedEvents.contains(.regionDidChange) { - let event = RNMBXEvent(type:.regionDidChange, payload: self.buildRegionObject()); - self.fireEvent(event: event, callback: self.reactOnMapChange) - } else if self.handleMapChangedEvents.contains(.mapIdle) { - let event = RNMBXEvent(type:.mapIdle, payload: self.buildStateObject()); - self.fireEvent(event: event, callback: self.reactOnMapChange) - } - - self.wasGestureActive = false - }) + withMapView { [weak self] _mapView in + guard let self = self else { return } + self.onEvery(event: .cameraChanged, handler: { (self, cameraEvent) in + self.wasGestureActive = self.isGestureActive + if self.handleMapChangedEvents.contains(.regionIsChanging) { + let event = RNMBXEvent(type:.regionIsChanging, payload: self.buildRegionObject(_mapView: _mapView)) + self.fireEvent(event: event, callback: self.reactOnMapChange) + } else if self.handleMapChangedEvents.contains(.cameraChanged) { + let event = RNMBXCameraChanged(type:.cameraChanged, payload: self.buildStateObject(_mapView: _mapView), reactTag: self.reactTag) + self.eventDispatcher.send(event) + } + }) + + self.onEvery(event: .mapIdle, handler: { (self, cameraEvent) in + if self.handleMapChangedEvents.contains(.regionDidChange) { + let event = RNMBXEvent(type:.regionDidChange, payload: self.buildRegionObject(_mapView: _mapView)); + self.fireEvent(event: event, callback: self.reactOnMapChange) + } else if self.handleMapChangedEvents.contains(.mapIdle) { + let event = RNMBXEvent(type:.mapIdle, payload: self.buildStateObject(_mapView: _mapView)); + self.fireEvent(event: event, callback: self.reactOnMapChange) + } + + self.wasGestureActive = false + }) + } } private func fireEvent(event: RNMBXEvent, callback: RCTBubblingEventBlock?) { @@ -1014,21 +1071,21 @@ extension RNMBXMapView { private func fireEvent(event: RNMBXEvent, callback: @escaping RCTBubblingEventBlock) { callback(event.toJSON()) } - - private func buildStateObject() -> [String: Any] { - let cameraOptions = CameraOptions(cameraState: mapView.cameraState) - let bounds = mapView.mapboxMap.coordinateBounds(for: cameraOptions) - + + private func buildStateObject(_mapView: MapView) -> [String: Any] { + let cameraOptions = CameraOptions(cameraState: _mapView.cameraState) + let bounds = _mapView.mapboxMap.coordinateBounds(for: cameraOptions) + return [ "properties": [ - "center": Point(mapView.cameraState.center).coordinates.toArray(), + "center": Point(_mapView.cameraState.center).coordinates.toArray(), "bounds": [ "ne": bounds.northeast.toArray(), "sw": bounds.southwest.toArray() ], - "zoom" : Double(mapView.cameraState.zoom), - "heading": Double(mapView.cameraState.bearing), - "pitch": Double(mapView.cameraState.pitch), + "zoom" : Double(_mapView.cameraState.zoom), + "heading": Double(_mapView.cameraState.bearing), + "pitch": Double(_mapView.cameraState.pitch), ], "gestures": [ "isGestureActive": wasGestureActive @@ -1041,22 +1098,22 @@ extension RNMBXMapView { return (date ?? Date()).timeIntervalSince1970 * 1000 } - private func buildRegionObject() -> [String: Any] { - let cameraOptions = CameraOptions(cameraState: mapView.cameraState) - let bounds = mapView.mapboxMap.coordinateBounds(for: cameraOptions) + private func buildRegionObject(_mapView: MapView) -> [String: Any] { + let cameraOptions = CameraOptions(cameraState: _mapView.cameraState) + let bounds = _mapView.mapboxMap.coordinateBounds(for: cameraOptions) let boundsArray : JSONArray = [ [.number(bounds.northeast.longitude),.number(bounds.northeast.latitude)], [.number(bounds.southwest.longitude),.number(bounds.southwest.latitude)] ] var result = Feature( - geometry: .point(Point(mapView.cameraState.center)) + geometry: .point(Point(_mapView.cameraState.center)) ) result.properties = [ - "zoomLevel": .number(mapView.cameraState.zoom), - "heading": .number(mapView.cameraState.bearing), - "bearing": .number(mapView.cameraState.bearing), - "pitch": .number(mapView.cameraState.pitch), + "zoomLevel": .number(_mapView.cameraState.zoom), + "heading": .number(_mapView.cameraState.bearing), + "bearing": .number(_mapView.cameraState.bearing), + "pitch": .number(_mapView.cameraState.pitch), "visibleBounds": .array(boundsArray), "isUserInteraction": .boolean(wasGestureActive), ] @@ -1066,75 +1123,78 @@ extension RNMBXMapView { } public func setupEvents() { - self.onEvery(event: .mapLoadingError, handler: { (self, event) in - let eventPayload : MapLoadingErrorPayload = event.payload - #if RNMBX_11 - let error = eventPayload - #else - let error = eventPayload.error - #endif - var payload : [String:String] = [ - "error": error.errorDescription ?? error.localizedDescription - ] - if let tileId = eventPayload.tileId { - payload["tileId"] = "x:\(tileId.x) y:\(tileId.y) z:\(tileId.z)" - } - if let sourceId = eventPayload.sourceId { - payload["sourceId"] = sourceId - } - let RNMBXEvent = RNMBXEvent(type: .mapLoadingError, payload: payload); - self.fireEvent(event: RNMBXEvent, callback: self.reactOnMapChange) - - if let message = error.errorDescription { - Logger.log(level: .error, message: "MapLoad error \(message)") - } else { - Logger.log(level: .error, message: "MapLoad error \(event)") - } - }) - - self.onEvery(event: .styleImageMissing) { (self, event) in - let imageName = event.payload.id - - self.images.forEach { - if $0.addMissingImageToStyle(style: self.mapboxMap.style, imageName: imageName) { - return + withMapboxMap { [weak self] _mapboxMap in + guard let self = self else { return } + self.onEvery(event: .mapLoadingError, handler: { (self, event) in + let eventPayload : MapLoadingErrorPayload = event.payload + #if RNMBX_11 + let error = eventPayload + #else + let error = eventPayload.error + #endif + var payload : [String:String] = [ + "error": error.errorDescription ?? error.localizedDescription + ] + if let tileId = eventPayload.tileId { + payload["tileId"] = "x:\(tileId.x) y:\(tileId.y) z:\(tileId.z)" } - } + if let sourceId = eventPayload.sourceId { + payload["sourceId"] = sourceId + } + let RNMBXEvent = RNMBXEvent(type: .mapLoadingError, payload: payload); + self.fireEvent(event: RNMBXEvent, callback: self.reactOnMapChange) - self.images.forEach { - $0.sendImageMissingEvent(imageName: imageName, payload: event.payload) - } - } + if let message = error.errorDescription { + Logger.log(level: .error, message: "MapLoad error \(message)") + } else { + Logger.log(level: .error, message: "MapLoad error \(event)") + } + }) + + self.onEvery(event: .styleImageMissing) { (self, event) in + let imageName = event.payload.id + + self.images.forEach { + if $0.addMissingImageToStyle(style: _mapboxMap.style, imageName: imageName) { + return + } + } - self.onEvery(event: .renderFrameFinished, handler: { (self, event) in - var type = RNMBXEvent.EventType.didFinishRendering - if event.payload.renderMode == .full { - type = .didFinishRenderingFully + self.images.forEach { + $0.sendImageMissingEvent(imageName: imageName, payload: event.payload) + } } - let payload : [String:Any] = [ - "renderMode": event.payload.renderMode.rawValue, - "needsRepaint": event.payload.needsRepaint, - "placementChanged": event.payload.placementChanged - ] - let event = RNMBXEvent(type: type, payload: payload); - self.fireEvent(event: event, callback: self.reactOnMapChange) - }) - self.onNext(event: .mapLoaded, handler: { (self, event) in - let event = RNMBXEvent(type:.didFinishLoadingMap, payload: nil); - self.fireEvent(event: event, callback: self.reactOnMapChange) - }) - - self.onEvery(event: .styleLoaded, handler: { (self, event) in - self.addFeaturesToMap(style: self.mapboxMap.style) + self.onEvery(event: .renderFrameFinished, handler: { (self, event) in + var type = RNMBXEvent.EventType.didFinishRendering + if event.payload.renderMode == .full { + type = .didFinishRenderingFully + } + let payload : [String:Any] = [ + "renderMode": event.payload.renderMode.rawValue, + "needsRepaint": event.payload.needsRepaint, + "placementChanged": event.payload.placementChanged + ] + let event = RNMBXEvent(type: type, payload: payload); + self.fireEvent(event: event, callback: self.reactOnMapChange) + }) - if !self.styleLoadWaiters.hasInited(), let mapboxMap = self.mapboxMap { - self.styleLoadWaiters.onInit(mapboxMap) - } + self.onNext(event: .mapLoaded, handler: { (self, event) in + let event = RNMBXEvent(type:.didFinishLoadingMap, payload: nil); + self.fireEvent(event: event, callback: self.reactOnMapChange) + }) + + self.onEvery(event: .styleLoaded, handler: { (self, event) in + self.addFeaturesToMap(style: _mapboxMap.style) + + if !self.styleLoadWaiters.hasInited() { + self.styleLoadWaiters.onInit(_mapboxMap) + } - let event = RNMBXEvent(type:.didFinishLoadingStyle, payload: nil) - self.fireEvent(event: event, callback: self.reactOnMapChange) - }) + let event = RNMBXEvent(type:.didFinishLoadingStyle, payload: nil) + self.fireEvent(event: event, callback: self.reactOnMapChange) + }) + } } } @@ -1200,13 +1260,16 @@ extension RNMBXMapView { } func applyOnPress() { - let singleTapGestureRecognizer = self.mapView.gestures.singleTapGestureRecognizer + withMapView { [weak self] _mapView in + guard let self = self else { return } + let singleTapGestureRecognizer = _mapView.gestures.singleTapGestureRecognizer - singleTapGestureRecognizer.removeTarget(pointAnnotationManager.manager, action: nil) - singleTapGestureRecognizer.addTarget(self, action: #selector(doHandleTap(_:))) + singleTapGestureRecognizer.removeTarget(self.getPointAnnotationManager(_mapView: _mapView).manager, action: nil) + singleTapGestureRecognizer.addTarget(self, action: #selector(self.doHandleTap(_:))) - self.tapDelegate = IgnoreRNMBXMakerViewGestureDelegate(originalDelegate: singleTapGestureRecognizer.delegate) - singleTapGestureRecognizer.delegate = tapDelegate + self.tapDelegate = IgnoreRNMBXMakerViewGestureDelegate(originalDelegate: singleTapGestureRecognizer.delegate) + singleTapGestureRecognizer.delegate = self.tapDelegate + } } @objc public func setReactOnLongPress(_ value: @escaping RCTBubblingEventBlock) { @@ -1215,9 +1278,12 @@ extension RNMBXMapView { } func applyOnLongPress() { - if (reactOnLongPress != nil) { - let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(doHandleLongPress(_:))) - self.mapView.addGestureRecognizer(longPressGestureRecognizer) + withMapView { [weak self] _mapView in + guard let self = self else { return } + if (self.reactOnLongPress != nil) { + let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.doHandleLongPress(_:))) + _mapView.addGestureRecognizer(longPressGestureRecognizer) + } } } } @@ -1240,7 +1306,13 @@ extension RNMBXMapView: GestureManagerDelegate { return sources.filter { $0.isTouchable() } } - private func doHandleTapInSources(sources: [RNMBXInteractiveElement], tapPoint: CGPoint, hits: [String: [QueriedRenderedFeature]], touchedSources: [RNMBXInteractiveElement], callback: @escaping (_ hits: [String: [QueriedRenderedFeature]], _ touchedSources: [RNMBXInteractiveElement]) -> Void) { + private func doHandleTapInSources( + sources: [RNMBXInteractiveElement], + tapPoint: CGPoint, + hits: [String: [QueriedRenderedFeature]], + touchedSources: [RNMBXInteractiveElement], + _mapboxMap: MapboxMap, + callback: @escaping (_ hits: [String: [QueriedRenderedFeature]], _ touchedSources: [RNMBXInteractiveElement]) -> Void) { DispatchQueue.main.async { if let source = sources.first { let hitbox = source.hitbox; @@ -1256,7 +1328,7 @@ extension RNMBXMapView: GestureManagerDelegate { let options = RenderedQueryOptions( layerIds: source.getLayerIDs(), filter: nil ) - self.mapboxMap.queryRenderedFeatures(with: hitboxRect, options: options) { + _mapboxMap.queryRenderedFeatures(with: hitboxRect, options: options) { result in var newHits = hits @@ -1273,15 +1345,15 @@ extension RNMBXMapView: GestureManagerDelegate { } var nSources = sources nSources.removeFirst() - self.doHandleTapInSources(sources: nSources, tapPoint: tapPoint, hits: newHits, touchedSources: newTouchedSources, callback: callback) + self.doHandleTapInSources(sources: nSources, tapPoint: tapPoint, hits: newHits, touchedSources: newTouchedSources, _mapboxMap: _mapboxMap, callback: callback) } } else { callback(hits, touchedSources) } } } - - func highestZIndex(sources: [RNMBXInteractiveElement]) -> RNMBXInteractiveElement? { + + func highestZIndex(sources: [RNMBXInteractiveElement], _mapboxMap: MapboxMap) -> RNMBXInteractiveElement? { var layersToSource : [String:RNMBXInteractiveElement] = [:] sources.forEach { source in @@ -1291,14 +1363,14 @@ extension RNMBXMapView: GestureManagerDelegate { } } } - let orderedLayers = mapboxMap.style.allLayerIdentifiers + let orderedLayers = _mapboxMap.style.allLayerIdentifiers return orderedLayers.lazy.reversed().compactMap { layersToSource[$0.id] }.first ?? sources.first } - func _tapEvent(_ tapPoint: CGPoint) -> RNMBXEvent { - let location = self.mapboxMap.coordinate(for: tapPoint) + func _tapEvent(_ tapPoint: CGPoint, _mapboxMap: MapboxMap) -> RNMBXEvent { + let location = _mapboxMap.coordinate(for: tapPoint) var geojson = Feature(geometry: .point(Point(location))); geojson.properties = [ "screenPointX": .number(Double(tapPoint.x)), @@ -1310,46 +1382,50 @@ extension RNMBXMapView: GestureManagerDelegate { @objc func doHandleTap(_ sender: UITapGestureRecognizer) { - let tapPoint = sender.location(in: self) - pointAnnotationManager.handleTap(sender) { (_: UITapGestureRecognizer) in - DispatchQueue.main.async { - if (self.deselectAnnotationOnTap) { - if (self.pointAnnotationManager.deselectCurrentlySelected(deselectAnnotationOnTap: true)) { - return - } - } - let touchableSources = self.touchableSources() - self.doHandleTapInSources(sources: touchableSources, tapPoint: tapPoint, hits: [:], touchedSources: []) { (hits, touchedSources) in - - if let source = self.highestZIndex(sources: touchedSources), - source.hasPressListener, - let onPress = source.onPress { - guard let hitFeatures = hits[source.id] else { - Logger.log(level:.error, message: "doHandleTap, no hits found when it should have") + withMapView { [weak self] _mapView in + guard let self = self else { return } + let tapPoint = sender.location(in: self) + let pointAnnotationManager = self.getPointAnnotationManager(_mapView: _mapView) + pointAnnotationManager.handleTap(sender) { (_: UITapGestureRecognizer) in + DispatchQueue.main.async { + if (self.deselectAnnotationOnTap) { + if (pointAnnotationManager.deselectCurrentlySelected(deselectAnnotationOnTap: true)) { return } - let features = hitFeatures.compactMap { queriedFeature in - logged("doHandleTap.hitFeatures") { try queriedFeature.feature.toJSON() } } - let location = self.mapboxMap.coordinate(for: tapPoint) - let event = RNMBXEvent( - type: (source is RNMBXVectorSource) ? .vectorSourceLayerPress : .shapeSourceLayerPress, - payload: [ - "features": features, - "point": [ - "x": Double(tapPoint.x), - "y": Double(tapPoint.y), - ], - "coordinates": [ - "latitude": Double(location.latitude), - "longitude": Double(location.longitude), + } + let touchableSources = self.touchableSources() + self.doHandleTapInSources(sources: touchableSources, tapPoint: tapPoint, hits: [:], touchedSources: [], _mapboxMap: _mapView.mapboxMap) { (hits, touchedSources) in + + if let source = self.highestZIndex(sources: touchedSources, _mapboxMap: _mapView.mapboxMap), + source.hasPressListener, + let onPress = source.onPress { + guard let hitFeatures = hits[source.id] else { + Logger.log(level:.error, message: "doHandleTap, no hits found when it should have") + return + } + let features = hitFeatures.compactMap { queriedFeature in + logged("doHandleTap.hitFeatures") { try queriedFeature.feature.toJSON() } } + let location = _mapView.mapboxMap.coordinate(for: tapPoint) + let event = RNMBXEvent( + type: (source is RNMBXVectorSource) ? .vectorSourceLayerPress : .shapeSourceLayerPress, + payload: [ + "features": features, + "point": [ + "x": Double(tapPoint.x), + "y": Double(tapPoint.y), + ], + "coordinates": [ + "latitude": Double(location.latitude), + "longitude": Double(location.longitude), + ] ] - ] - ) - self.fireEvent(event: event, callback: onPress) - - } else { - if let reactOnPress = self.reactOnPress { - self.fireEvent(event: self._tapEvent(tapPoint), callback: reactOnPress) + ) + self.fireEvent(event: event, callback: onPress) + + } else { + if let reactOnPress = self.reactOnPress { + self.fireEvent(event: self._tapEvent(tapPoint, _mapboxMap: _mapView.mapboxMap), callback: reactOnPress) + } } } } @@ -1359,48 +1435,51 @@ extension RNMBXMapView: GestureManagerDelegate { @objc func doHandleLongPress(_ sender: UILongPressGestureRecognizer) { - let position = sender.location(in: self) - pointAnnotationManager.handleLongPress(sender) { (_: UILongPressGestureRecognizer) in - DispatchQueue.main.async { - let draggableSources = self.draggableSources() - self.doHandleTapInSources(sources: draggableSources, tapPoint: position, hits: [:], touchedSources: []) { (hits, draggedSources) in - if let source = self.highestZIndex(sources: draggedSources), - source.draggable, - let onDragStart = source.onDragStart { - guard let hitFeatures = hits[source.id] else { - Logger.log(level:.error, message: "doHandleLongPress, no hits found when it should have") - return - } - let features = hitFeatures.compactMap { queriedFeature in - logged("doHandleTap.hitFeatures") { try queriedFeature.feature.toJSON() } } - let location = self.mapboxMap.coordinate(for: position) - let event = RNMBXEvent( - type: .longPress, - payload: [ - "features": features, - "point": [ - "x": Double(position.x), - "y": Double(position.y), - ], - "coordinates": [ - "latitude": Double(location.latitude), - "longitude": Double(location.longitude), + withMapView { [weak self] _mapView in + guard let self = self else { return } + let position = sender.location(in: self) + self.getPointAnnotationManager(_mapView: _mapView).handleLongPress(sender) { (_: UILongPressGestureRecognizer) in + DispatchQueue.main.async { + let draggableSources = self.draggableSources() + self.doHandleTapInSources(sources: draggableSources, tapPoint: position, hits: [:], touchedSources: [], _mapboxMap: _mapView.mapboxMap) { (hits, draggedSources) in + if let source = self.highestZIndex(sources: draggedSources, _mapboxMap: _mapView.mapboxMap), + source.draggable, + let onDragStart = source.onDragStart { + guard let hitFeatures = hits[source.id] else { + Logger.log(level:.error, message: "doHandleLongPress, no hits found when it should have") + return + } + let features = hitFeatures.compactMap { queriedFeature in + logged("doHandleTap.hitFeatures") { try queriedFeature.feature.toJSON() } } + let location = _mapView.mapboxMap.coordinate(for: position) + let event = RNMBXEvent( + type: .longPress, + payload: [ + "features": features, + "point": [ + "x": Double(position.x), + "y": Double(position.y), + ], + "coordinates": [ + "latitude": Double(location.latitude), + "longitude": Double(location.longitude), + ] ] - ] - ) - self.fireEvent(event: event, callback: onDragStart) - } else { - if let reactOnLongPress = self.reactOnLongPress, sender.state == .began { - let coordinate = self.mapboxMap.coordinate(for: position) - var geojson = Feature(geometry: .point(Point(coordinate))); - geojson.properties = [ - "screenPointX": .number(Double(position.x)), - "screenPointY": .number(Double(position.y)) - ] - let event = RNMBXEvent(type:.longPress, payload: logged("doHandleLongPress") { try geojson.toJSON() }) - self.fireEvent(event: event, callback: reactOnLongPress) + ) + self.fireEvent(event: event, callback: onDragStart) + } else { + if let reactOnLongPress = self.reactOnLongPress, sender.state == .began { + let coordinate = _mapView.mapboxMap.coordinate(for: position) + var geojson = Feature(geometry: .point(Point(coordinate))); + geojson.properties = [ + "screenPointX": .number(Double(position.x)), + "screenPointY": .number(Double(position.y)) + ] + let event = RNMBXEvent(type:.longPress, payload: logged("doHandleLongPress") { try geojson.toJSON() }) + self.fireEvent(event: event, callback: reactOnLongPress) + } } - } + } } } } @@ -1423,6 +1502,22 @@ extension RNMBXMapView: GestureManagerDelegate { extension RNMBXMapView { + @objc public func dumpState() -> String { + let featuresDesc = self.features.map { [ + "addedToMap": $0.addedToMap, + "feature": $0.feature.description + ]} + let sourcesDesc = self.sources.map {[ + "id": $0.id, + "map": $0.map == self + ]} + let stateDict = ["featuresDesc": featuresDesc, "sourcesDesc": sourcesDesc] + if let jsonData = try? JSONSerialization.data(withJSONObject: stateDict, options: []), let jsonString = String(data: jsonData, encoding: .utf8) { + return jsonString + } + return "Failed to encode RNMBXMapView state" + } + @objc public func takeSnap( writeToDisk:Bool) -> URL { @@ -1437,18 +1532,17 @@ extension RNMBXMapView } extension RNMBXMapView { - func queryTerrainElevation(coordinates: [NSNumber]) -> Double? { - return self.mapboxMap.elevation(at: CLLocationCoordinate2D(latitude: coordinates[1].doubleValue, longitude: coordinates[0].doubleValue)) + func queryTerrainElevation(coordinates: [NSNumber], _mapboxMap: MapboxMap) -> Double? { + return _mapboxMap.elevation(at: CLLocationCoordinate2D(latitude: coordinates[1].doubleValue, longitude: coordinates[0].doubleValue)) } } extension RNMBXMapView { func onMapStyleLoaded(block: @escaping (MapboxMap) -> Void) { - guard let mapboxMap = mapboxMap else { - fatalError("mapboxMap is null") + withMapboxMap { [weak self] _mapboxMap in + guard let self = self else { return } + self.styleLoadWaiters.callOrWait(block) } - - styleLoadWaiters.callOrWait(block) } } @@ -1480,32 +1574,34 @@ func getLayerSourceDetails(layer: (any Layer)?) -> LayerSourceDetails? { extension RNMBXMapView { func setSourceVisibility(_ visible: Bool, sourceId: String, sourceLayerId: String?) -> Void { - let style = self.mapboxMap.style - - style.allLayerIdentifiers.forEach { layerInfo in - let layer = logged("setSourceVisibility.layer", info: { "\(layerInfo.id)" }) { - try style.layer(withId: layerInfo.id) - } + withMapboxMap { _mapboxMap in + let style = _mapboxMap.style + + style.allLayerIdentifiers.forEach { layerInfo in + let layer = logged("setSourceVisibility.layer", info: { "\(layerInfo.id)" }) { + try style.layer(withId: layerInfo.id) + } - #if RNMBX_11 - let sourceDetails = getLayerSourceDetails(layer: layer) - #else - let sourceDetails: LayerSourceDetails? = (source: layer?.source, sourceLayer: layer?.sourceLayer) - #endif - - if let layer = layer, let sourceDetails = sourceDetails { - if sourceDetails.source == sourceId { - var good = true - if let sourceLayerId = sourceLayerId { - if sourceLayerId != sourceDetails.sourceLayer { - good = false + #if RNMBX_11 + let sourceDetails = getLayerSourceDetails(layer: layer) + #else + let sourceDetails: LayerSourceDetails? = (source: layer?.source, sourceLayer: layer?.sourceLayer) + #endif + + if let layer = layer, let sourceDetails = sourceDetails { + if sourceDetails.source == sourceId { + var good = true + if let sourceLayerId = sourceLayerId { + if sourceLayerId != sourceDetails.sourceLayer { + good = false + } } - } - if good { - do { - try style.setLayerProperty(for: layer.id, property: "visibility", value: visible ? "visible" : "none") - } catch { - Logger.log(level: .error, message: "Cannot change visibility of \(layer.id) with source: \(sourceId)") + if good { + do { + try style.setLayerProperty(for: layer.id, property: "visibility", value: visible ? "visible" : "none") + } catch { + Logger.log(level: .error, message: "Cannot change visibility of \(layer.id) with source: \(sourceId)") + } } } } @@ -1681,65 +1777,67 @@ class RNMBXPointAnnotationManager : AnnotationInteractionDelegate { return } let options = RenderedQueryOptions(layerIds: [layerId], filter: nil) - guard let targetPoint = self.mapView?.mapboxMap.coordinate(for: sender.location(in: sender.view)) else { - return - } - switch sender.state { - case .began: - mapFeatureQueryable.queryRenderedFeatures( - with: sender.location(in: sender.view), - options: options) { [weak self] (result) in - - guard let self = self else { return } - switch result { - case .success(let queriedFeatures): - // Get the identifiers of all the queried features - let queriedFeatureIds: [String] = queriedFeatures.compactMap { - guard case let .string(featureId) = $0.feature.identifier else { - return nil - } - return featureId - } - - // Find if any `queriedFeatureIds` match an annotation's `id` - let draggedAnnotations = self.manager.annotations.filter { queriedFeatureIds.contains($0.id) } - let enabledAnnotations = draggedAnnotations.filter { self.lookup($0)?.draggable ?? false } - // If `tappedAnnotations` is not empty, call delegate - if !enabledAnnotations.isEmpty { - self.draggedAnnotation = enabledAnnotations.first! - self.onDragHandler(self.manager, didDetectDraggedAnnotations: enabledAnnotations, dragState: .began, targetPoint: targetPoint) - } else { + withMapView { _mapView in + guard let targetPoint = _mapView.mapboxMap.coordinate(for: sender.location(in: sender.view)) else { + return + } + switch sender.state { + case .began: + mapFeatureQueryable.queryRenderedFeatures( + with: sender.location(in: sender.view), + options: options) { [weak self] (result) in + + guard let self = self else { return } + switch result { + case .success(let queriedFeatures): + // Get the identifiers of all the queried features + let queriedFeatureIds: [String] = queriedFeatures.compactMap { + guard case let .string(featureId) = $0.feature.identifier else { + return nil + } + return featureId + } + + // Find if any `queriedFeatureIds` match an annotation's `id` + let draggedAnnotations = self.manager.annotations.filter { queriedFeatureIds.contains($0.id) } + let enabledAnnotations = draggedAnnotations.filter { self.lookup($0)?.draggable ?? false } + // If `tappedAnnotations` is not empty, call delegate + if !enabledAnnotations.isEmpty { + self.draggedAnnotation = enabledAnnotations.first! + self.onDragHandler(self.manager, didDetectDraggedAnnotations: enabledAnnotations, dragState: .began, targetPoint: targetPoint) + } else { + noAnnotationFound(sender) + } + case .failure(let error): noAnnotationFound(sender) + Logger.log(level:.warn, message:"Failed to query map for annotations due to error: \(error)") } - case .failure(let error): - noAnnotationFound(sender) - Logger.log(level:.warn, message:"Failed to query map for annotations due to error: \(error)") } - } - case .changed: - guard var annotation = self.draggedAnnotation else { - return - } + case .changed: + guard var annotation = self.draggedAnnotation else { + return + } - self.onDragHandler(self.manager, didDetectDraggedAnnotations: [annotation], dragState: .changed, targetPoint: targetPoint) + self.onDragHandler(self.manager, didDetectDraggedAnnotations: [annotation], dragState: .changed, targetPoint: targetPoint) - let idx = self.manager.annotations.firstIndex { an in return an.id == annotation.id } - if let idx = idx { - self.manager.annotations[idx].point = Point(targetPoint) + let idx = self.manager.annotations.firstIndex { an in return an.id == annotation.id } + if let idx = idx { + self.manager.annotations[idx].point = Point(targetPoint) + } + case .cancelled, .ended: + guard let annotation = self.draggedAnnotation else { + return } - case .cancelled, .ended: - guard let annotation = self.draggedAnnotation else { + // Optionally notify some other delegate to tell them the drag finished. + self.onDragHandler(self.manager, didDetectDraggedAnnotations: [annotation], dragState: .ended, targetPoint: targetPoint) + // Reset our global var containing the annotation currently being dragged + self.draggedAnnotation = nil + return + default: return } - // Optionally notify some other delegate to tell them the drag finished. - self.onDragHandler(self.manager, didDetectDraggedAnnotations: [annotation], dragState: .ended, targetPoint: targetPoint) - // Reset our global var containing the annotation currently being dragged - self.draggedAnnotation = nil - return - default: - return - } + } } func remove(_ annotation: PointAnnotation) { diff --git a/ios/RNMBX/RNMBXMapViewManager.swift b/ios/RNMBX/RNMBXMapViewManager.swift index bdcbf3eb41..71ca4554e2 100644 --- a/ios/RNMBX/RNMBXMapViewManager.swift +++ b/ios/RNMBX/RNMBXMapViewManager.swift @@ -35,7 +35,14 @@ import MapboxMaps // MARK: - react methods extension RNMBXMapViewManager { + @objc public static func dumpState(_ view: RNMBXMapView, + resolver: @escaping RCTPromiseResolveBlock) { + let encodedState = view.dumpState() + resolver(["encodedState": encodedState]) + } + @objc public static func takeSnap( + _ view: RNMBXMapView, writeToDisk: Bool, resolver: @escaping RCTPromiseResolveBlock @@ -50,11 +57,14 @@ extension RNMBXMapViewManager { resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock ) { - let result = view.queryTerrainElevation(coordinates: coordinates) - if let result = result { - resolver(["data": NSNumber(value: result)]) - } else { - resolver(nil) + view.withMapboxMap { [weak view] _mapboxMap in + guard let view = view else { return } + let result = view.queryTerrainElevation(coordinates: coordinates, _mapboxMap: _mapboxMap) + if let result = result { + resolver(["data": NSNumber(value: result)]) + } else { + resolver(nil) + } } } @@ -193,34 +203,37 @@ extension RNMBXMapViewManager { resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock ) { - let top = bbox.isEmpty ? 0.0 : CGFloat(bbox[0].floatValue) - let right = bbox.isEmpty ? 0.0 : CGFloat(bbox[1].floatValue) - let bottom = bbox.isEmpty ? 0.0 : CGFloat(bbox[2].floatValue) - let left = bbox.isEmpty ? 0.0 : CGFloat(bbox[3].floatValue) - let rect = - bbox.isEmpty - ? CGRect(x: 0.0, y: 0.0, width: map.bounds.size.width, height: map.bounds.size.height) - : CGRect( - x: [left, right].min()!, y: [top, bottom].min()!, width: abs(right - left), - height: abs(bottom - top)) - logged("queryRenderedFeaturesInRect.option", rejecter: rejecter) { - let options = try RenderedQueryOptions( - layerIds: layerIDs?.isEmpty ?? true ? nil : layerIDs, filter: filter?.asExpression()) - map.mapboxMap.queryRenderedFeatures(with: rect, options: options) { result in - switch result { - case .success(let features): - resolver([ - "data": [ - "type": "FeatureCollection", - "features": features.compactMap { queriedFeature in - logged("queryRenderedFeaturesInRect.queriedfeature.map") { - try queriedFeature.feature.toJSON() - } - }, - ] - ]) - case .failure(let error): - rejecter("queryRenderedFeaturesInRect", "failed to query features", error) + map.withMapboxMap { [weak map] _mapboxMap in + guard let map = map else { return } + let top = bbox.isEmpty ? 0.0 : CGFloat(bbox[0].floatValue) + let right = bbox.isEmpty ? 0.0 : CGFloat(bbox[1].floatValue) + let bottom = bbox.isEmpty ? 0.0 : CGFloat(bbox[2].floatValue) + let left = bbox.isEmpty ? 0.0 : CGFloat(bbox[3].floatValue) + let rect = + bbox.isEmpty + ? CGRect(x: 0.0, y: 0.0, width: map.bounds.size.width, height: map.bounds.size.height) + : CGRect( + x: [left, right].min()!, y: [top, bottom].min()!, width: abs(right - left), + height: abs(bottom - top)) + logged("queryRenderedFeaturesInRect.option", rejecter: rejecter) { + let options = try RenderedQueryOptions( + layerIds: layerIDs?.isEmpty ?? true ? nil : layerIDs, filter: filter?.asExpression()) + _mapboxMap.queryRenderedFeatures(with: rect, options: options) { result in + switch result { + case .success(let features): + resolver([ + "data": [ + "type": "FeatureCollection", + "features": features.compactMap { queriedFeature in + logged("queryRenderedFeaturesInRect.queriedfeature.map") { + try queriedFeature.feature.toJSON() + } + }, + ] + ]) + case .failure(let error): + rejecter("queryRenderedFeaturesInRect", "failed to query features", error) + } } } } @@ -234,38 +247,42 @@ extension RNMBXMapViewManager { resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock ) { - let sourceLayerIds = sourceLayerIds?.isEmpty ?? true ? nil : sourceLayerIds - logged("querySourceFeatures.option", rejecter: rejecter) { - let options = SourceQueryOptions( - sourceLayerIds: sourceLayerIds, filter: filter ?? Exp(arguments: [])) - map.mapboxMap.querySourceFeatures(for: sourceId, options: options) { result in - switch result { - case .success(let features): - resolver([ - "data": [ - "type": "FeatureCollection", - "features": features.compactMap { queriedFeature in - logged("querySourceFeatures.queriedfeature.map") { - try queriedFeature.feature.toJSON() - } - }, - ] as [String: Any] - ]) - case .failure(let error): - rejecter( - "querySourceFeatures", - "failed to query source features: \(error.localizedDescription)", error) + map.withMapboxMap { _mapboxMap in + let sourceLayerIds = sourceLayerIds?.isEmpty ?? true ? nil : sourceLayerIds + logged("querySourceFeatures.option", rejecter: rejecter) { + let options = SourceQueryOptions( + sourceLayerIds: sourceLayerIds, filter: filter ?? Exp(arguments: [])) + _mapboxMap.querySourceFeatures(for: sourceId, options: options) { result in + switch result { + case .success(let features): + resolver([ + "data": [ + "type": "FeatureCollection", + "features": features.compactMap { queriedFeature in + logged("querySourceFeatures.queriedfeature.map") { + try queriedFeature.feature.toJSON() + } + }, + ] as [String: Any] + ]) + case .failure(let error): + rejecter( + "querySourceFeatures", + "failed to query source features: \(error.localizedDescription)", error) + } } } } } static func clearData(_ view: RNMBXMapView, completion: @escaping (Error?) -> Void) { - #if RNMBX_11 - MapboxMap.clearData(completion: completion) - #else - view.mapboxMap.clearData(completion: completion) - #endif + view.withMapboxMap { _mapboxMap in + #if RNMBX_11 + MapboxMap.clearData(completion: completion) + #else + _mapboxMap.clearData(completion: completion) + #endif + } } @objc public static func clearData( diff --git a/ios/RNMBX/RNMBXMapViewModule.mm b/ios/RNMBX/RNMBXMapViewModule.mm index ec622d2235..3ada4d70bb 100644 --- a/ios/RNMBX/RNMBXMapViewModule.mm +++ b/ios/RNMBX/RNMBXMapViewModule.mm @@ -46,6 +46,11 @@ - (void)withMapView:(nonnull NSNumber*)viewRef block:(void (^)(RNMBXMapView *))b }]; } +RCT_EXPORT_METHOD(dumpState:(nonnull NSNumber*)viewRef resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { + [self withMapView:viewRef block:^(RNMBXMapView *view) { + [RNMBXMapViewManager dumpState:view resolver:resolve]; + } reject:reject methodName:@"dumpState"]; +} RCT_EXPORT_METHOD(takeSnap:(nonnull NSNumber*)viewRef writeToDisk:(BOOL)writeToDisk resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { diff --git a/ios/RNMBX/RNMBXMarkerView.swift b/ios/RNMBX/RNMBXMarkerView.swift index 3998d23e2d..386b3f3522 100644 --- a/ios/RNMBX/RNMBXMarkerView.swift +++ b/ios/RNMBX/RNMBXMarkerView.swift @@ -12,11 +12,11 @@ class RNMBXMarkerViewParentViewAnnotation : UIView { required init?(coder: NSCoder) { fatalError("not implented") } - + func remove(marker: RNMBXMarkerView) { marker.removeFromSuperview() } - + func updateSize(_ size: CGSize, oldOffset: CGVector, newOffset: CGVector) { let actSize = self.frame.size if actSize.width != size.width || actSize.height != size.height { @@ -42,6 +42,12 @@ public class RNMBXMarkerView: UIView, RNMBXMapComponent { var didAddToMap = false + public override var description: String { + return "debugLabel=\(debugLabel), id=\(id), coordinate=\(coordinate), anchor=\(anchor), map=\(map == nil ? "nil" : String(describing: ObjectIdentifier(map!)))" + } + + @objc public var debugLabel: String? + @objc public var coordinate: Array? { didSet { update() @@ -71,36 +77,36 @@ public class RNMBXMarkerView: UIView, RNMBXMapComponent { update() } } - + // MARK: - Derived variables - var annotationManager: ViewAnnotationManager? { - self.map?.mapView?.viewAnnotations + func getAnnotationManager(_mapView: MapView) -> ViewAnnotationManager { + _mapView.viewAnnotations } - + var point: Point? { guard let _lat = coordinate?[1] else { - Logger.log(level: .error, message: "[getPoint] No latitude were set") - return nil + Logger.log(level: .error, message: "[getPoint] No latitude were set") + return nil } guard let _lon = coordinate?[0] else { - Logger.log(level: .error, message: "[getPoint] No Longitude were set") - return nil + Logger.log(level: .error, message: "[getPoint] No Longitude were set") + return nil } let coord = CLLocationCoordinate2D( - latitude: Double(_lat) as CLLocationDegrees, longitude: Double(_lon) as CLLocationDegrees); - + latitude: Double(_lat) as CLLocationDegrees, longitude: Double(_lon) as CLLocationDegrees); + return Point(coord) } - + // MARK: - RNMBXMapComponent methods - + public func addToMap(_ map: RNMBXMapView, style: Style) { self.map = map add() } - + public func removeFromMap(_ map: RNMBXMapView, reason: RemovalReason) -> Bool { remove() return true @@ -108,7 +114,7 @@ public class RNMBXMarkerView: UIView, RNMBXMapComponent { // MARK: - React methods - public override var isHidden: Bool { + public override var isHidden: Bool { get { return super.isHidden } @@ -117,7 +123,7 @@ public class RNMBXMarkerView: UIView, RNMBXMapComponent { } } - public override func reactSetFrame(_ frame: CGRect) { + public override func reactSetFrame(_ frame: CGRect) { let prev = self.frame var next = frame @@ -130,81 +136,91 @@ public class RNMBXMarkerView: UIView, RNMBXMapComponent { height: next.height ) } - + super.reactSetFrame(next) if frameDidChange { - updateAnnotationViewSize(next, prev) + updateAnnotationViewSize(next, prev) } addOrUpdate() } - - public override func insertReactSubview(_ subview: UIView, at atIndex: Int) { - super.insertReactSubview(subview, at: atIndex) - } - - public override func removeReactSubview(_ subview: UIView) { - super.removeReactSubview(subview) - } - + + public override func insertReactSubview(_ subview: UIView, at atIndex: Int) { + super.insertReactSubview(subview, at: atIndex) + } + + public override func removeReactSubview(_ subview: UIView) { + super.removeReactSubview(subview) + } + @objc public func updateAnnotationViewSize(_ next: CGRect, _ prev: CGRect) { annotationView.updateSize(next.size, oldOffset:calcOffset(size: prev.size), newOffset: calcOffset(size: next.size)) } - + public func waitForStyleLoad() -> Bool { true } - + // MARK: - Create, update, and remove methods - - @objc public func addOrUpdate() { - if didAddToMap { - update() - } else { - add() - } + + @objc public func addOrUpdate() { + if didAddToMap { + update() + } else { + add() } + } /// Because the necessary data to add an annotation arrives from different sources at unpredictable times, we let the arrival of each value trigger an attempt to add the annotation, which we only do if all of the data exists, and the annotation not been added already. private func add() { - if didAddToMap { - return - } - - guard let annotationManager = annotationManager, let _ = point else { - return - } - - do { - let options = getOptions() - try annotationManager.add(annotationView, id: id, options: options) - didAddToMap = true - } catch { - Logger.log(level: .error, message: "[MarkerView] Error adding annotation", error: error) + print("[rnmapbox] [RNMBXMarkerView] add \(self.description)") + self.map?.withMapView { _mapView in + let annotationManager = self.getAnnotationManager(_mapView: _mapView) + + if self.didAddToMap { + return + } + + guard let _ = self.point else { + return + } + + do { + let options = self.getOptions() + try annotationManager.add(self.annotationView, id: self.id, options: options) + self.didAddToMap = true + } catch { + Logger.log(level: .error, message: "[MarkerView] Error adding annotation", error: error) + } } } - + private func update() { - if !didAddToMap { - return - } - - guard let annotationManager = annotationManager else { - return - } - - do { - let options = getOptions() - try annotationManager.update(annotationView, options: options) - } catch { - Logger.log(level: .error, message: "[MarkerView] Error updating annotation", error: error) + print("[rnmapbox] [RNMBXMarkerView] update \(self.description)") + self.map?.withMapView { _mapView in + let annotationManager = self.getAnnotationManager(_mapView: _mapView) + + if !self.didAddToMap { + return + } + + do { + let options = self.getOptions() + try annotationManager.update(self.annotationView, options: options) + } catch { + Logger.log(level: .error, message: "[MarkerView] Error updating annotation", error: error) + } } } private func remove() { - annotationManager?.remove(annotationView) - annotationView.remove(marker: self) - self._annotationView = nil - didAddToMap = false + print("[rnmapbox] [RNMBXMarkerView] remove \(self.description)") + self.map?.withMapView { _mapView in + let annotationManager = self.getAnnotationManager(_mapView: _mapView) + annotationManager.remove(self.annotationView) + self.annotationView.remove(marker: self) + self._annotationView = nil + self.didAddToMap = false + } } // MARK: - Helper functions @@ -217,7 +233,7 @@ public class RNMBXMarkerView: UIView, RNMBXMapComponent { let size = self.bounds.size let offset = calcOffset(size: size) - + var options = ViewAnnotationOptions( geometry: geometry, width: size.width, @@ -227,10 +243,10 @@ public class RNMBXMarkerView: UIView, RNMBXMapComponent { offsetY: offset.dy, selected: isSelected ) - #if RNMBX_11 +#if RNMBX_11 options.allowOverlapWithPuck = allowOverlapWithPuck options.ignoreCameraPadding = true - #endif +#endif return options } @@ -238,10 +254,10 @@ public class RNMBXMarkerView: UIView, RNMBXMapComponent { guard let anchor = anchor, let anchorX = anchor["x"]?.CGFloat, let anchorY = anchor["y"]?.CGFloat else { return .zero } - + let x = (anchorX * 2 - 1) * (size.width / 2) * -1 let y = (anchorY * 2 - 1) * (size.height / 2) - + return CGVector(dx: x, dy: y) } @@ -253,8 +269,8 @@ public class RNMBXMarkerView: UIView, RNMBXMapComponent { _annotationView = result return result } - - @objc public override func didMoveToSuperview() { + + @objc public override func didMoveToSuperview() { // React tends to add back us to our original superview, // https://github.com/facebook/react-native/blob/11ece22fc6955d169def9ef9f2809c24bc457ba8/React/Views/UIView%2BReact.m#L172-L177 // fix that if we see that diff --git a/ios/RNMBX/RNMBXMarkerViewContentManager.m b/ios/RNMBX/RNMBXMarkerViewContentManager.m index 72f9928792..f4f5fe2bd0 100644 --- a/ios/RNMBX/RNMBXMarkerViewContentManager.m +++ b/ios/RNMBX/RNMBXMarkerViewContentManager.m @@ -1,4 +1,4 @@ -#import "React/RCTBridgeModule.h" +#import #import #import diff --git a/ios/RNMBX/RNMBXMarkerViewManager.m b/ios/RNMBX/RNMBXMarkerViewManager.m index c0ab14dce9..b9de366eea 100644 --- a/ios/RNMBX/RNMBXMarkerViewManager.m +++ b/ios/RNMBX/RNMBXMarkerViewManager.m @@ -1,9 +1,10 @@ -#import "React/RCTBridgeModule.h" +#import #import #import @interface RCT_EXTERN_REMAP_MODULE(RNMBXMarkerView, RNMBXMarkerViewManager, RCTViewManager) +RCT_EXPORT_VIEW_PROPERTY(debugLabel, NSString) RCT_EXPORT_VIEW_PROPERTY(coordinate, NSArray) RCT_EXPORT_VIEW_PROPERTY(anchor, NSDictionary) RCT_EXPORT_VIEW_PROPERTY(allowOverlap, BOOL) diff --git a/ios/RNMBX/RNMBXModule.m b/ios/RNMBX/RNMBXModule.m index 3b0af79ad1..e00b5089e3 100644 --- a/ios/RNMBX/RNMBXModule.m +++ b/ios/RNMBX/RNMBXModule.m @@ -1,4 +1,4 @@ -#import "React/RCTBridgeModule.h" +#import @interface RCT_EXTERN_MODULE(RNMBXModule, NSObject) diff --git a/ios/RNMBX/RNMBXNativeUserLocation.swift b/ios/RNMBX/RNMBXNativeUserLocation.swift index 534e2154c2..aafcfe2388 100644 --- a/ios/RNMBX/RNMBXNativeUserLocation.swift +++ b/ios/RNMBX/RNMBXNativeUserLocation.swift @@ -131,72 +131,70 @@ public class RNMBXNativeUserLocation: UIView, RNMBXMapComponent { guard let map = self.map else { return } - guard let mapView = map.mapView else { - Logger.error("RNMBXNativeUserLocation mapView was nil") - return - } - guard let location = mapView.location else { - Logger.error("RNMBXNativeUserLocation location was nil") - return - } + map.withMapView { _mapView in + guard let location = _mapView.location else { + Logger.error("RNMBXNativeUserLocation location was nil") + return + } - if (!visible) { - let emptyImage = UIGraphicsImageRenderer(size: CGSize(width: 1, height: 1)).image { _ in } - location.options.puckType = .puck2D( - Puck2DConfiguration( - topImage: emptyImage, - bearingImage: emptyImage, - shadowImage: emptyImage, - scale: Value.constant(1.0) + if (!self.visible) { + let emptyImage = UIGraphicsImageRenderer(size: CGSize(width: 1, height: 1)).image { _ in } + location.options.puckType = .puck2D( + Puck2DConfiguration( + topImage: emptyImage, + bearingImage: emptyImage, + shadowImage: emptyImage, + scale: Value.constant(1.0) + ) ) - ) - return - } else { - var configuration : Puck2DConfiguration = images.isEmpty ? - .makeDefault(showBearing: puckBearingEnabled) : Puck2DConfiguration( - topImage: self.images[.top], - bearingImage: self.images[.bearing], - shadowImage: self.images[.shadow]) - - if let scale = toDoubleValue(value: scale, name: "scale") { - configuration.scale = scale - } + return + } else { + var configuration : Puck2DConfiguration = self.images.isEmpty ? + .makeDefault(showBearing: self.puckBearingEnabled) : Puck2DConfiguration( + topImage: self.images[.top], + bearingImage: self.images[.bearing], + shadowImage: self.images[.shadow]) + + if let scale = self.toDoubleValue(value: self.scale, name: "scale") { + configuration.scale = scale + } - if let pulsing = pulsing { - if let kind = pulsing["kind"] as? String, kind == "default" { - configuration.pulsing = .default - } else { - var pulsingConfig = Puck2DConfiguration.Pulsing() - if let isEnabled = pulsing["isEnabled"] as? Bool { - pulsingConfig.isEnabled = isEnabled - } + if let pulsing = self.pulsing { + if let kind = pulsing["kind"] as? String, kind == "default" { + configuration.pulsing = .default + } else { + var pulsingConfig = Puck2DConfiguration.Pulsing() + if let isEnabled = pulsing["isEnabled"] as? Bool { + pulsingConfig.isEnabled = isEnabled + } - if let radius = pulsing["radius"] as? String { - if radius == "accuracy" { - pulsingConfig.radius = .accuracy - } else { - Logger.log(level: .error, message: "expected pulsing/radius to be either a number or accuracy but was \(radius)") + if let radius = pulsing["radius"] as? String { + if radius == "accuracy" { + pulsingConfig.radius = .accuracy + } else { + Logger.log(level: .error, message: "expected pulsing/radius to be either a number or accuracy but was \(radius)") + } + } else if let radius = pulsing["radius"] as? NSNumber { + pulsingConfig.radius = .constant(radius.doubleValue) } - } else if let radius = pulsing["radius"] as? NSNumber { - pulsingConfig.radius = .constant(radius.doubleValue) - } - if let color = pulsing["color"] { - if let uicolor = RCTConvert.uiColor(color) { - pulsingConfig.color = uicolor - } else { - Logger.log(level: .error, message: "expected color to be a color but was \(color)") + if let color = pulsing["color"] { + if let uicolor = RCTConvert.uiColor(color) { + pulsingConfig.color = uicolor + } else { + Logger.log(level: .error, message: "expected color to be a color but was \(color)") + } } - } - configuration.pulsing = pulsingConfig + configuration.pulsing = pulsingConfig + } } + location.options.puckType = .puck2D(configuration) + } + location.options.puckBearingEnabled = self.puckBearingEnabled + if let puckBearing = self._puckBearing { + location.options.puckBearing = puckBearing } - location.options.puckType = .puck2D(configuration) - } - location.options.puckBearingEnabled = puckBearingEnabled - if let puckBearing = _puckBearing { - location.options.puckBearing = puckBearing } } @@ -208,14 +206,16 @@ public class RNMBXNativeUserLocation: UIView, RNMBXMapComponent { } public func removeFromMap(_ map: RNMBXMapView, reason: RemovalReason) -> Bool { - if let location = map.mapView.location { - location.options.puckType = nil - location.options.puckType = .none - } else { - Logger.error("RNMBXNativeUserLocation.removeFromMap: location is nil") + map.withMapView { _mapView in + if let location = _mapView.location { + location.options.puckType = nil + location.options.puckType = .none + } else { + Logger.error("RNMBXNativeUserLocation.removeFromMap: location is nil") + } + self.removeSubscriptions() + self.map = nil } - removeSubscriptions() - self.map = nil return true } @@ -248,26 +248,29 @@ extension RNMBXNativeUserLocation { } func _fetchImages(_ map: RNMBXMapView) { - if let style = map.mapView?.mapboxMap?.style { - imageNames.forEach { (part, name) in - if let name = name { - if style.imageExists(withId: name), let image = style.image(withId: name) { - images[part] = image + map.withMapView { [weak map] _mapView in + guard let map = map else { return } + if let style = _mapView.mapboxMap?.style { + self.imageNames.forEach { (part, name) in + if let name = name { + if style.imageExists(withId: name), let image = style.image(withId: name) { + self.images[part] = image + } else { + self.images.removeValue(forKey: part) + } } else { - images.removeValue(forKey: part) + self.images.removeValue(forKey: part) } - } else { - images.removeValue(forKey: part) } } - } - let imageManager = map.imageManager - removeSubscriptions() - self.imageManager = imageManager - imageNames.forEach { (part,name) in - if let name = name { - subscribe(imageManager, part, name) + let imageManager = map.imageManager + self.removeSubscriptions() + self.imageManager = imageManager + self.imageNames.forEach { (part,name) in + if let name = name { + self.subscribe(imageManager, part, name) + } } } } diff --git a/ios/RNMBX/RNMBXPointAnnotation.swift b/ios/RNMBX/RNMBXPointAnnotation.swift index fbe524cca8..adbf4523c0 100644 --- a/ios/RNMBX/RNMBXPointAnnotation.swift +++ b/ios/RNMBX/RNMBXPointAnnotation.swift @@ -155,10 +155,10 @@ public class RNMBXPointAnnotation : RNMBXInteractiveElement { return image } - func makeEvent(isSelect: Bool, deselectAnnotationOnMapTap: Bool = false) -> RNMBXEvent { + func makeEvent(isSelect: Bool, _mapboxMap: MapboxMap, deselectAnnotationOnMapTap: Bool = false) -> RNMBXEvent { let position = superview?.convert(layer.position, to: nil) - let location = map?.mapboxMap.coordinate(for: position!) - var geojson = Feature(geometry: .point(Point(location!))) + let location = _mapboxMap.coordinate(for: position!) + var geojson = Feature(geometry: .point(Point(location))) geojson.identifier = .string(id) var properties : [String: JSONValue?] = [ "screenPointX": .number(Double(position!.x)), @@ -173,19 +173,25 @@ public class RNMBXPointAnnotation : RNMBXInteractiveElement { } func doSelect() { - let event = makeEvent(isSelect: true) - if let onSelected = onSelected { - onSelected(event.toJSON()) + self.map?.withMapboxMap { [weak self] _mapboxMap in + guard let self = self else { return } + let event = self.makeEvent(isSelect: true, _mapboxMap: _mapboxMap) + if let onSelected = self.onSelected { + onSelected(event.toJSON()) + } + self.onSelect() } - onSelect() } func doDeselect(deselectAnnotationOnMapTap: Bool = false) { - let event = makeEvent(isSelect: false, deselectAnnotationOnMapTap: deselectAnnotationOnMapTap) - if let onDeselected = onDeselected { - onDeselected(event.toJSON()) + self.map?.withMapboxMap { [weak self] _mapboxMap in + guard let self = self else { return } + let event = self.makeEvent(isSelect: false, _mapboxMap: _mapboxMap, deselectAnnotationOnMapTap: deselectAnnotationOnMapTap) + if let onDeselected = self.onDeselected { + onDeselected(event.toJSON()) + } + self.onDeselect() } - onDeselect() } func onSelect() { @@ -200,13 +206,17 @@ public class RNMBXPointAnnotation : RNMBXInteractiveElement { if let size = image?.size { calloutPtAnnotation.iconOffset = [0, -size.height] } - self.map?.calloutAnnotationManager.annotations.append(calloutPtAnnotation) + self.map?.withMapView { _mapView in + self.map?.getCalloutAnnotationManager(_mapView: _mapView).annotations.append(calloutPtAnnotation) + } } } func onDeselect() { - self.map?.calloutAnnotationManager.annotations.removeAll { - $0.id == calloutId + self.map?.withMapView { _mapView in + self.map?.getCalloutAnnotationManager(_mapView: _mapView).annotations.removeAll { + $0.id == self.calloutId + } } } @@ -253,7 +263,9 @@ public class RNMBXPointAnnotation : RNMBXInteractiveElement { public override func addToMap(_ map: RNMBXMapView, style: Style) { super.addToMap(map, style: style) self.map = map - addIfPossible() + self.map?.withMapView { _mapView in + self.addIfPossible(_mapView: _mapView) + } } public override func removeFromMap(_ map: RNMBXMapView, reason: RemovalReason) -> Bool { @@ -273,18 +285,20 @@ public class RNMBXPointAnnotation : RNMBXInteractiveElement { extension RNMBXPointAnnotation { func removeIfAdded() { - if added, let pointAnnotationManager = map?.pointAnnotationManager { - pointAnnotationManager.remove(annotation) - added = false + self.map?.withMapView { _mapView in + if self.added, let pointAnnotationManager = self.map?.getPointAnnotationManager(_mapView: _mapView) { + pointAnnotationManager.remove(self.annotation) + self.added = false + } } } @discardableResult - func addIfPossible() -> Bool { + func addIfPossible(_mapView: MapView) -> Bool { if !added && annotation.point.coordinates.isValid() && (logged("PointAnnotation: missing id attribute") { return id }) != nil, - let pointAnnotationManager = map?.pointAnnotationManager { + let pointAnnotationManager = map?.getPointAnnotationManager(_mapView: _mapView) { pointAnnotationManager.add(annotation, self) added = true return true @@ -294,11 +308,13 @@ extension RNMBXPointAnnotation { func update(callback: (_ annotation: inout PointAnnotation) -> Void) { callback(&annotation) - if let pointAnnotationManager = map?.pointAnnotationManager { - if added { - pointAnnotationManager.update(annotation) - } else if !added { - addIfPossible() + self.map?.withMapView { _mapView in + if let pointAnnotationManager = self.map?.getPointAnnotationManager(_mapView: _mapView) { + if self.added { + pointAnnotationManager.update(self.annotation) + } else if !self.added { + self.addIfPossible(_mapView: _mapView) + } } } } diff --git a/ios/RNMBX/RNMBXPointAnnotationViewManager.m b/ios/RNMBX/RNMBXPointAnnotationViewManager.m index 6fa19e57c8..54d0ff9975 100644 --- a/ios/RNMBX/RNMBXPointAnnotationViewManager.m +++ b/ios/RNMBX/RNMBXPointAnnotationViewManager.m @@ -1,4 +1,4 @@ -#import "React/RCTBridgeModule.h" +#import #import #import diff --git a/ios/RNMBX/RNMBXShapeSource.swift b/ios/RNMBX/RNMBXShapeSource.swift index fcabaf3fd1..5359ca77b3 100644 --- a/ios/RNMBX/RNMBXShapeSource.swift +++ b/ios/RNMBX/RNMBXShapeSource.swift @@ -9,24 +9,33 @@ public class RNMBXShapeSource : RNMBXSource { didSet { parseJSON(url) { [weak self] result in guard let self = self else { return } - + switch result { - case .success(let obj): - self.doUpdate { (style) in + case .success(let obj): + self.map?.withMapboxMap { [weak self] _mapboxMap in + guard let self = self else { return } + self.doUpdate(_mapboxMap: _mapboxMap) { (style) in logged(LOG_TAG, "setUrl") { try style.updateGeoJSONSource(withId: self.id, geoJSON: obj) } } - case .failure(let error): - Logger.log(level: .error, message: "Update url failed", error: error) + } + case .failure(let error): + Logger.log(level: .error, message: "Update url failed", error: error) } } } } - + + public override var description: String { + return "debugLabel=\(debugLabel), id=\(id), url=\(url), shape=\(shape)" + } + var shapeAnimator: ShapeAnimator? = nil var shapeObject: GeoJSONObject? = nil - + + @objc public var debugLabel: String? + @objc public var shape : String? { didSet { shapeAnimator?.unsubscribe(consumer: self) @@ -45,9 +54,12 @@ public class RNMBXShapeSource : RNMBXSource { let obj : GeoJSONObject = try parse(shape) shapeObject = obj - doUpdate { (style) in - logged(LOG_TAG, "setShape") { - try style.updateGeoJSONSource(withId: id, geoJSON: obj) + self.map?.withMapboxMap { [weak self] _mapboxMap in + guard let self = self else { return } + self.doUpdate(_mapboxMap: _mapboxMap) { (style) in + logged(LOG_TAG, "setShape") { + try style.updateGeoJSONSource(withId: self.id, geoJSON: obj) + } } } } @@ -56,25 +68,30 @@ public class RNMBXShapeSource : RNMBXSource { } public override func addToMap(_ map: RNMBXMapView, style: Style) { + print("[rnmapbox] [RNMBXShapeSource] addToMap \(self.description)") super.addToMap(map, style: style) } public override func removeFromMap(_ map: RNMBXMapView, reason: RemovalReason) -> Bool { + print("[rnmapbox] [RNMBXShapeSource] removeFrom \(self.description)") if (reason == .ViewRemoval) { shapeAnimator?.unsubscribe(consumer: self) } return super.removeFromMap(map, reason: reason) } - + @objc public var cluster : NSNumber? @objc public var clusterRadius : NSNumber? @objc public var clusterMaxZoomLevel : NSNumber? { didSet { logged(LOG_TAG, "clusterMaxZoomLevel") { if let number = clusterMaxZoomLevel?.doubleValue { - doUpdate { (style) in - logged(LOG_TAG, "clusterMaxZoomLevel") { - try style.setSourceProperty(for: id, property: "clusterMaxZoom", value: number) + self.map?.withMapboxMap { [weak self] _mapboxMap in + guard let self = self else { return } + self.doUpdate(_mapboxMap: _mapboxMap) { (style) in + logged(LOG_TAG, "clusterMaxZoomLevel") { + try style.setSourceProperty(for: self.id, property: "clusterMaxZoom", value: number) + } } } } @@ -82,7 +99,7 @@ public class RNMBXShapeSource : RNMBXSource { } } @objc public var clusterProperties : [String: [Any]]?; - + @objc public var maxZoomLevel : NSNumber? @objc public var buffer : NSNumber? @objc public var tolerance : NSNumber? @@ -91,58 +108,58 @@ public class RNMBXShapeSource : RNMBXSource { override func sourceType() -> Source.Type { return GeoJSONSource.self } - + override func makeSource() -> Source { - #if RNMBX_11 +#if RNMBX_11 var result = GeoJSONSource(id: id) - #else +#else var result = GeoJSONSource() - #endif - +#endif + if let shapeObject = shapeObject { result.data = toGeoJSONSourceData(shapeObject) } else { result.data = emptyShape() } - + if let url = url { result.data = .url(URL(string: url)!) } - + if let cluster = cluster { result.cluster = cluster.boolValue } - + if let clusterRadius = clusterRadius { result.clusterRadius = clusterRadius.doubleValue } - + if let clusterMaxZoomLevel = clusterMaxZoomLevel { result.clusterMaxZoom = clusterMaxZoomLevel.doubleValue } - + do { if let clusterProperties = clusterProperties { result.clusterProperties = try clusterProperties.mapValues { (params : [Any]) in let data = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted) let decodedExpression = try JSONDecoder().decode(Expression.self, from: data) - + return decodedExpression } } } catch { Logger.log(level: .error, tag: LOG_TAG, message: "makeSource: parsing clusterProperties failed", error: error) } - + if let maxZoomLevel = maxZoomLevel { result.maxzoom = maxZoomLevel.doubleValue } - + if let buffer = buffer { result.buffer = buffer.doubleValue } - + if let tolerance = tolerance { result.tolerance = tolerance.doubleValue } @@ -151,21 +168,25 @@ public class RNMBXShapeSource : RNMBXSource { return result } - - func doUpdate(_ update:(Style) -> Void) { + + func doUpdate(_mapboxMap: MapboxMap, _ update:(Style) -> Void) { guard let map = self.map, let _ = self.source, - map.mapboxMap.style.sourceExists(withId: id) else { + _mapboxMap.style.sourceExists(withId: id) else { return } - - let style = map.mapboxMap.style + + let style = _mapboxMap.style update(style) } - + func updateSource(property: String, value: Any) { - doUpdate { style in - try! style.setSourceProperty(for: id, property: property, value: value) + print("[rnmapbox] [RNMBXShapeSource] updateSource \(self.description)") + self.map?.withMapboxMap { [weak self] _mapboxMap in + guard let self = self else { return } + self.doUpdate(_mapboxMap: _mapboxMap) { style in + try! style.setSourceProperty(for: self.id, property: property, value: value) + } } } @@ -188,22 +209,22 @@ extension RNMBXShapeSource return .featureCollection(features) } } - + func parseJSON(_ url: String?, completion: @escaping (Result) -> Void) { guard let url = url else { return } - + DispatchQueue.global().async { [url] in let result: Result - + do { let data = try Data(contentsOf: URL(string: url)!) let obj = try JSONDecoder().decode(GeoJSONObject.self, from: data) - + result = .success(obj) } catch { result = .failure(error) } - + DispatchQueue.main.async { completion(result) } @@ -232,14 +253,14 @@ extension RNMBXShapeSource } } } - + func parse(_ shape: String) throws -> Feature { guard let data = shape.data(using: .utf8) else { throw RNMBXError.parseError("shape is not utf8") } return try JSONDecoder().decode(Feature.self, from: data) } - + func parse(_ shape: String?) throws -> GeoJSONObject { guard let shape = shape else { return emptyGeoJSONObject() @@ -254,29 +275,29 @@ extension RNMBXShapeSource return .featureCollection(featureCollection) case .geometry(let geometry): return .geometry(geometry) - #if RNMBX_11 +#if RNMBX_11 case .string(_): // RNMBX_11_TODO throw RNMBXError.parseError("url as shape is not supported when updating a ShapeSource") - #else +#else case .url(_): throw RNMBXError.parseError("url as shape is not supported when updating a ShapeSource") - #endif +#endif } } - + func emptyGeoJSONObject() -> GeoJSONObject { return .featureCollection(emptyFeatureCollection()) } - + func emptyShape() -> GeoJSONSourceData { return GeoJSONSourceData.featureCollection(FeatureCollection(features:[])) } - + func emptyFeatureCollection() -> FeatureCollection { return FeatureCollection(features:[]) } - + func parseAsJSONObject(shape: String?) -> Any? { guard let shape = shape else { return nil @@ -316,26 +337,26 @@ extension MapboxMap { feature: Feature, completion: @escaping (Result) -> Void) -> Cancelable { self.queryFeatureExtension(for: sourceId, - feature: feature, - extension: "supercluster", - extensionField: "children", - args: nil, - completion: completion) + feature: feature, + extension: "supercluster", + extensionField: "children", + args: nil, + completion: completion) return DummyCancellable() } - + @discardableResult public func getGeoJsonClusterLeaves(forSourceId sourceId: String, feature: Feature, limit: UInt64 = 10, offset: UInt64 = 0, completion: @escaping (Result) -> Void) -> Cancelable { - self.queryFeatureExtension(for: sourceId, - feature: /*MapboxCommon.Feature(*/feature/*)*/, - extension: "supercluster", - extensionField: "leaves", - args: ["limit": limit, "offset": offset], - completion: completion) + self.queryFeatureExtension(for: sourceId, + feature: /*MapboxCommon.Feature(*/feature/*)*/, + extension: "supercluster", + extensionField: "leaves", + args: ["limit": limit, "offset": offset], + completion: completion) return DummyCancellable() } } @@ -350,75 +371,66 @@ extension RNMBXShapeSource _ featureJSON: String, completion: @escaping (Result) -> Void) { - guard let mapView = map?.mapView else { - completion(.failure(RNMBXError.failed("getClusterExpansionZoom: no mapView"))) - return - } - - logged(LOG_TAG, "getClusterExpansionZoom", rejecter: { (_,_,error) in - completion(.failure(error!)) - }) { - let cluster : Feature = try parse(featureJSON); - - mapView.mapboxMap.getGeoJsonClusterExpansionZoom(forSourceId: self.id, feature: cluster) { result in - switch result { - case .success(let features): - guard let value = features.value as? NSNumber else { - completion(.failure(RNMBXError.failed("getClusterExpansionZoom: not a number"))) - return + self.map?.withMapView { _mapView in + logged(LOG_TAG, "getClusterExpansionZoom", rejecter: { (_,_,error) in + completion(.failure(error!)) + }) { + let cluster : Feature = try self.parse(featureJSON); + + _mapView.mapboxMap.getGeoJsonClusterExpansionZoom(forSourceId: self.id, feature: cluster) { result in + switch result { + case .success(let features): + guard let value = features.value as? NSNumber else { + completion(.failure(RNMBXError.failed("getClusterExpansionZoom: not a number"))) + return + } + + completion(.success(value.intValue)) + case .failure(let error): + completion(.failure(error)) } - - completion(.success(value.intValue)) - case .failure(let error): - completion(.failure(error)) } } } } - + func getClusterLeaves(_ featureJSON: String, - number: uint, - offset: uint, - completion: @escaping (Result) -> Void) + number: uint, + offset: uint, + completion: @escaping (Result) -> Void) { - guard let mapView = map?.mapView else { - completion(.failure(RNMBXError.failed("getClusterLeaves: no mapView"))) - return - } - - logged(LOG_TAG, "getClusterLeaves", rejecter: { (_,_,error) in - completion(.failure(error!)) - }) { - let cluster : Feature = try parse(featureJSON); - mapView.mapboxMap.getGeoJsonClusterLeaves(forSourceId: self.id, feature: cluster, limit: UInt64(number), offset: UInt64(offset)) { - result in - switch result { - case .success(let features): - completion(.success(features)) - case .failure(let error): - completion(.failure(error)) + self.map?.withMapView { _mapView in + logged(LOG_TAG, "getClusterLeaves", rejecter: { (_,_,error) in + completion(.failure(error!)) + }) { + let cluster : Feature = try self.parse(featureJSON); + _mapView.mapboxMap.getGeoJsonClusterLeaves(forSourceId: self.id, feature: cluster, limit: UInt64(number), offset: UInt64(offset)) { + result in + switch result { + case .success(let features): + completion(.success(features)) + case .failure(let error): + completion(.failure(error)) + } } } } } - + func getClusterChildren(_ featureJSON: String, completion: @escaping (Result) -> Void) { - guard let mapView = map?.mapView else { - completion(.failure(RNMBXError.failed("getClusterChildren: no mapView"))) - return - } - - logged(LOG_TAG, "getClusterChildren", rejecter: { (_,_,error) in - completion(.failure(error!)) - }) { - let cluster : Feature = try parse(featureJSON); - mapView.mapboxMap.getGeoJsonClusterChildren(forSourceId: self.id, feature: cluster) { - result in - switch result { - case .success(let features): - completion(.success(features)) - case .failure(let error): - completion(.failure(error)) + self.map?.withMapView { _mapView in + logged(LOG_TAG, "getClusterChildren", rejecter: { (_,_,error) in + completion(.failure(error!)) + }) { + let cluster : Feature = try self.parse(featureJSON); + _mapView.mapboxMap.getGeoJsonClusterChildren(forSourceId: self.id, feature: cluster) { + result in + switch result { + case .success(let features): + completion(.success(features)) + case .failure(let error): + completion(.failure(error)) + } } } } @@ -429,10 +441,13 @@ extension RNMBXShapeSource extension RNMBXShapeSource: ShapeAnimationConsumer { func shapeUpdated(shape: Turf.GeoJSONObject) { - shapeObject = shape - doUpdate { (style) in - logged("RCTMGLShapeSource.setShape") { - try style.updateGeoJSONSource(withId: id, geoJSON: shape) + self.map?.withMapboxMap { [weak self] _mapboxMap in + guard let self = self else { return } + self.shapeObject = shape + self.doUpdate(_mapboxMap: _mapboxMap) { (style) in + logged("RCTMGLShapeSource.setShape") { + try style.updateGeoJSONSource(withId: self.id, geoJSON: shape) + } } } } diff --git a/ios/RNMBX/RNMBXShapeSourceViewManager.m b/ios/RNMBX/RNMBXShapeSourceViewManager.m index 7ef003d886..6efa6f9e59 100644 --- a/ios/RNMBX/RNMBXShapeSourceViewManager.m +++ b/ios/RNMBX/RNMBXShapeSourceViewManager.m @@ -6,6 +6,7 @@ RCT_EXTERN_REMAP_MODULE(RNMBXShapeSource, RNMBXShapeSourceViewManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(id, NSString) +RCT_EXPORT_VIEW_PROPERTY(debugLabel, NSString) RCT_EXPORT_VIEW_PROPERTY(existing, BOOL) RCT_EXPORT_VIEW_PROPERTY(url, NSString) RCT_EXPORT_VIEW_PROPERTY(shape, NSString) diff --git a/ios/RNMBX/RNMBXSource.swift b/ios/RNMBX/RNMBXSource.swift index 284fdfe4ef..f42e5c722f 100644 --- a/ios/RNMBX/RNMBXSource.swift +++ b/ios/RNMBX/RNMBXSource.swift @@ -36,19 +36,25 @@ public class RNMBXSource : RNMBXInteractiveElement { super.insertReactSubview(subview, at: atIndex) } - @objc public func insertReactSubviewInternal(_ subview: UIView!, at atIndex: Int) { - if let layer = subview as? RNMBXSourceConsumer { - if let map = map { - layer.addToMap(map, style: map.mapboxMap.style) + @objc public func insertReactSubviewInternal(_ subview: UIView!, at atIndex: Int) { + if let layer = subview as? RNMBXSourceConsumer { + if let map = map { + map.withMapboxMap { [weak map] _mapboxMap in + guard let map = map else { return } + layer.addToMap(map, style: _mapboxMap.style) } - layers.append(layer) - } else if let component = subview as? RNMBXMapComponent { - if let map = map { - component.addToMap(map, style: map.mapboxMap.style) + } + layers.append(layer) + } else if let component = subview as? RNMBXMapComponent { + if let map = map { + map.withMapboxMap { [weak map] _mapboxMap in + guard let map = map else { return } + component.addToMap(map, style: _mapboxMap.style) } - components.append(component) } - } + components.append(component) + } + } @objc public override func removeReactSubview(_ subview: UIView!) { removeReactSubviewInternal(subview) @@ -58,7 +64,10 @@ public class RNMBXSource : RNMBXInteractiveElement { @objc public func removeReactSubviewInternal(_ subview: UIView!) { if let layer : RNMBXSourceConsumer = subview as? RNMBXSourceConsumer { if let map = map { - layer.removeFromMap(map, style: map.mapboxMap.style) + map.withMapboxMap { [weak map] _mapboxMap in + guard let map = map else { return } + layer.removeFromMap(map, style: _mapboxMap.style) + } } layers.removeAll { $0 as AnyObject === layer } } else if let component = subview as? RNMBXMapComponent { @@ -101,10 +110,16 @@ public class RNMBXSource : RNMBXInteractiveElement { } for layer in self.layers { - layer.addToMap(map, style: map.mapboxMap.style) + map.withMapboxMap { [weak map] _mapboxMap in + guard let map = map else { return } + layer.addToMap(map, style: _mapboxMap.style) + } } for component in self.components { - component.addToMap(map, style: map.mapboxMap.style) + map.withMapboxMap { [weak map] _mapboxMap in + guard let map = map else { return } + component.addToMap(map, style: _mapboxMap.style) + } } } @@ -112,15 +127,20 @@ public class RNMBXSource : RNMBXInteractiveElement { self.map = nil for layer in self.layers { - layer.removeFromMap(map, style: map.mapboxMap.style) + map.withMapboxMap { [weak map] _mapboxMap in + guard let map = map else { return } + layer.removeFromMap(map, style: _mapboxMap.style) + } } if self.ownsSource { - let style = map.mapboxMap.style - logged("StyleSource.removeFromMap", info: { "id: \(optional: self.id)"}) { - try style.removeSource(withId: id) + map.withMapboxMap { [weak map] _mapboxMap in + let style = _mapboxMap.style + logged("StyleSource.removeFromMap", info: { "id: \(optional: self.id)"}) { + try style.removeSource(withId: self.id) + } + self.ownsSource = false } - self.ownsSource = false } return true } diff --git a/ios/RNMBX/RNMBXStyleImport.swift b/ios/RNMBX/RNMBXStyleImport.swift index 43ea62736d..34d7b25224 100644 --- a/ios/RNMBX/RNMBXStyleImport.swift +++ b/ios/RNMBX/RNMBXStyleImport.swift @@ -25,8 +25,9 @@ open class RNMBXStyleImport: UIView, RNMBXMapComponent { } public func addToMap(_ map: RNMBXMapView, style: Style) { - mapView = map.mapView - apply(mapView: map.mapView) + map.withMapView { _mapView in + self.apply(mapView: _mapView) + } } public func removeFromMap(_ map: RNMBXMapView, reason: RemovalReason) -> Bool { diff --git a/ios/RNMBX/RNMBXTerrain.swift b/ios/RNMBX/RNMBXTerrain.swift index e3cfdc5509..b9bf0a7b1c 100644 --- a/ios/RNMBX/RNMBXTerrain.swift +++ b/ios/RNMBX/RNMBXTerrain.swift @@ -25,12 +25,11 @@ public class RNMBXTerrain : RNMBXSingletonLayer, RNMBXMapComponent, RNMBXSourceC public func removeFromMap(_ map: RNMBXMapView, reason: RemovalReason) -> Bool { self.map = nil - guard let mapboxMap = map.mapboxMap else { - return true + map.withMapboxMap { [weak map] _mapboxMap in + guard let map = map else { return } + let style = _mapboxMap.style + self.removeFromMap(map, style: style) } - - let style = mapboxMap.style - removeFromMap(map, style: style) return true } diff --git a/ios/RNMBX/RNMBXViewport.swift b/ios/RNMBX/RNMBXViewport.swift index c0b4173941..b776c12104 100644 --- a/ios/RNMBX/RNMBXViewport.swift +++ b/ios/RNMBX/RNMBXViewport.swift @@ -57,16 +57,19 @@ open class RNMBXViewport : UIView, RNMBXMapComponent, ViewportStatusObserver { } public func addToMap(_ map: RNMBXMapView, style: Style) { - mapView = map.mapView - applyHasStatusChanged(mapView: mapView!) - apply(mapView: map.mapView) + map.withMapView { _mapView in + self.applyHasStatusChanged(mapView: _mapView) + self.apply(mapView: _mapView) + } } public func removeFromMap(_ map: RNMBXMapView, reason: RemovalReason) -> Bool { - if (hasStatusChanged) { - map.mapView.viewport.removeStatusObserver(self) + map.withMapView { _mapView in + if (self.hasStatusChanged) { + _mapView.viewport.removeStatusObserver(self) + } + self.mapView = nil } - self.mapView = nil return true } diff --git a/src/Mapbox.ts b/src/Mapbox.ts index f7d9ec1312..6aacd0841c 100644 --- a/src/Mapbox.ts +++ b/src/Mapbox.ts @@ -123,3 +123,10 @@ export const __experimental = { MovePointShapeAnimator, ChangeLineOffsetsShapeAnimator, }; + +const LineJoin = { + Bevel: 'bevel' as const, + Round: 'round' as const, + Miter: 'miter' as const, +} +export {LineJoin}; \ No newline at end of file diff --git a/src/components/MapView.tsx b/src/components/MapView.tsx index 3592714905..cc0672b642 100644 --- a/src/components/MapView.tsx +++ b/src/components/MapView.tsx @@ -838,6 +838,11 @@ class MapView extends NativeBridgeComponent( ); } + async dumpState(): Promise { + const res = await this._runNative<{ encodedState: string }>('dumpState'); + return res.encodedState; + } + /** * Takes snapshot of map with current tiles and returns a URI to the image * @param {Boolean} writeToDisk If true will create a temp file, otherwise it is in base64 diff --git a/src/components/MarkerView.tsx b/src/components/MarkerView.tsx index 91fc8876a7..8f4a46daf1 100644 --- a/src/components/MarkerView.tsx +++ b/src/components/MarkerView.tsx @@ -12,6 +12,9 @@ import PointAnnotation from './PointAnnotation'; const Mapbox = NativeModules.RNMBXModule; type Props = ViewProps & { + + debugLabel: string; + /** * The center point (specified as a map coordinate) of the marker. */ @@ -116,6 +119,7 @@ class MarkerView extends React.PureComponent { }, this.props.style, ]} + debugLabel={this.props.debugLabel} coordinate={[ Number(this.props.coordinate[0]), Number(this.props.coordinate[1]), diff --git a/src/components/ShapeSource.tsx b/src/components/ShapeSource.tsx index 8f752f5bf9..89f72c4aaa 100644 --- a/src/components/ShapeSource.tsx +++ b/src/components/ShapeSource.tsx @@ -32,6 +32,8 @@ export type Props = { */ id: string; + debugLabel: string; + /** * The id refers to en existing source in the style. Does not create a new source. */ diff --git a/src/specs/RNMBXMarkerViewNativeComponent.ts b/src/specs/RNMBXMarkerViewNativeComponent.ts index a73dcfd0d9..ccd8ea24a8 100644 --- a/src/specs/RNMBXMarkerViewNativeComponent.ts +++ b/src/specs/RNMBXMarkerViewNativeComponent.ts @@ -12,6 +12,7 @@ type Point = { }; export interface NativeProps extends ViewProps { + debugLabel: UnsafeMixed; coordinate?: UnsafeMixed; anchor: UnsafeMixed; allowOverlap: UnsafeMixed;