diff --git a/ios/RNMBX/RNMBXAtmosphere.swift b/ios/RNMBX/RNMBXAtmosphere.swift index e423da2ab..f8bed1341 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/RNMBXCamera.swift b/ios/RNMBX/RNMBXCamera.swift index 69c7e2777..f888da57a 100644 --- a/ios/RNMBX/RNMBXCamera.swift +++ b/ios/RNMBX/RNMBXCamera.swift @@ -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/RNMBXCustomLocationProvider.swift b/ios/RNMBX/RNMBXCustomLocationProvider.swift index 8ff3c5480..0bd7ed6a1 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 b60dee623..86028ba41 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 88b9a83f6..362a55787 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 de37d2d67..e0487d1a3 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 aab3f9f64..fe70facd8 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/RNMBXMapView.swift b/ios/RNMBX/RNMBXMapView.swift index 5f07618d0..a580283a0 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) + } } - } + } } } } @@ -1437,18 +1516,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 +1558,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 +1761,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 bdcbf3eb4..9beccaf37 100644 --- a/ios/RNMBX/RNMBXMapViewManager.swift +++ b/ios/RNMBX/RNMBXMapViewManager.swift @@ -50,11 +50,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 +196,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 +240,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/RNMBXMarkerView.swift b/ios/RNMBX/RNMBXMarkerView.swift index 3998d23e2..f57762db5 100644 --- a/ios/RNMBX/RNMBXMarkerView.swift +++ b/ios/RNMBX/RNMBXMarkerView.swift @@ -74,8 +74,8 @@ public class RNMBXMarkerView: UIView, RNMBXMapComponent { // MARK: - Derived variables - var annotationManager: ViewAnnotationManager? { - self.map?.mapView?.viewAnnotations + func getAnnotationManager(_mapView: MapView) -> ViewAnnotationManager { + _mapView.viewAnnotations } var point: Point? { @@ -166,45 +166,52 @@ public class RNMBXMarkerView: UIView, RNMBXMapComponent { /// 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 - } + self.map?.withMapView { _mapView in + let annotationManager = self.getAnnotationManager(_mapView: _mapView) - guard let annotationManager = annotationManager, let _ = point else { - return - } + if self.didAddToMap { + 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) + 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 - } + self.map?.withMapView { _mapView in + let annotationManager = self.getAnnotationManager(_mapView: _mapView) - guard let annotationManager = annotationManager else { - return - } + if !self.didAddToMap { + return + } - do { - let options = getOptions() - try annotationManager.update(annotationView, options: options) - } catch { - Logger.log(level: .error, message: "[MarkerView] Error updating annotation", error: error) + 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 + 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 diff --git a/ios/RNMBX/RNMBXNativeUserLocation.swift b/ios/RNMBX/RNMBXNativeUserLocation.swift index 534e2154c..aafcfe238 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 fbe524cca..adbf4523c 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/RNMBXShapeSource.swift b/ios/RNMBX/RNMBXShapeSource.swift index fcabaf3fd..ab5640e30 100644 --- a/ios/RNMBX/RNMBXShapeSource.swift +++ b/ios/RNMBX/RNMBXShapeSource.swift @@ -12,11 +12,14 @@ public class RNMBXShapeSource : RNMBXSource { switch result { case .success(let obj): - self.doUpdate { (style) in + 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) } @@ -45,9 +48,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) + } } } } @@ -72,9 +78,12 @@ public class RNMBXShapeSource : RNMBXSource { 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) + } } } } @@ -152,20 +161,23 @@ 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) + 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) + } } } @@ -350,27 +362,24 @@ 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); + 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 + } - 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)) } } } @@ -381,44 +390,38 @@ extension RNMBXShapeSource 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 +432,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/RNMBXSource.swift b/ios/RNMBX/RNMBXSource.swift index 284fdfe4e..f42e5c722 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 43ea62736..34d7b2522 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 e3cfdc550..b9bf0a7b1 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 c0b417394..b776c1210 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 }