Mapbox-gl-native: iOS MGLShapeSource cannot remove/add without crashing.

Created on 3 Oct 2017  路  2Comments  路  Source: mapbox/mapbox-gl-native

Platform:
iOS
Mapbox SDK version:
3.6.4

Steps to trigger behavior

  1. Add a shape source to the map with a given identifier
  2. After a network reload of the geometry data, attempt to remove that source from the map style and re-add it as a new source with the same identifier.
  3. Exception is thrown for same identifier error.

Here is the code that I'm using to try and do this. Here, the field annotation set object is just a struct that holds a pin annotation for the title of the field, and a multipoly for the actual geometry of the object. I have double-checked that this code is being called well after the map style is done loading.

func add(_ annotationSet: DCFieldAnnotationSet) {
        self.addAnnotation(annotationSet.fieldPinAnnotation)
        if let fieldName = annotationSet.fieldPinAnnotation.title {
            if let source = style?.source(withIdentifier: fieldName) {
                style?.removeSource(source)
            }
            if let styleLayer = style?.layer(withIdentifier: fieldName+"fillLayer") {
                style?.removeLayer(styleLayer)
            }
            let source = MGLShapeSource(identifier: fieldName, shape: annotationSet.fieldMultiPoly, options: nil)
            let styleLayer = MGLFillStyleLayer(identifier: fieldName, source: source)
            styleLayer.fillColor = MGLConstantStyleValue(rawValue: DCMapStyle.polygonFillColor)
            styleLayer.fillOpacity = MGLConstantStyleValue(rawValue: DCMapStyle.fillAlpha as NSNumber)
            style?.addSource(source)
            style?.addLayer(styleLayer)
        }
    }

Expected behavior

I need to be able to remove and replace map sources and style layers with objects with the same identifiers.

Actual behavior

I cannot remove and replace a source or a style layer without a duplicate object exception being thrown.

iOS runtime styling

Most helpful comment

Not trying to reopen this. Just pointing out that I discovered in my own work that order matters. You need to remove the layer _before_ removing the source, e.g:

    func removeLayer(mapView: MGLMapView, layerIdentifier: String){

        guard let currentLayers = mapView.style?.layers else { return }

        if currentLayers.filter({ $0.identifier == layerIdentifier}).first != nil {

            print("Layer \(layerIdentifier) found.")

            guard let mapStyle = mapView.style else { return }

            // remove layer first
            if let styleLayer = mapStyle.layer(withIdentifier: layerIdentifier) {
                mapStyle.removeLayer(styleLayer)
            }

            // then remove the source
            if let source = mapStyle.source(withIdentifier: layerIdentifier) {

                mapStyle.removeSource(source)
            }
        }
        else{

            print("No layer with the identifier \(layerIdentifier) found.")
        }
    }

All 2 comments

You may want to update the shape property on your source, rather than creating a new source. This allows the source to keep the original source identifier.

For example:

if let source = style?.source(withIdentifier: fieldName) {
                source.shape = annotationSet.fieldMultiPoly
            }

Not trying to reopen this. Just pointing out that I discovered in my own work that order matters. You need to remove the layer _before_ removing the source, e.g:

    func removeLayer(mapView: MGLMapView, layerIdentifier: String){

        guard let currentLayers = mapView.style?.layers else { return }

        if currentLayers.filter({ $0.identifier == layerIdentifier}).first != nil {

            print("Layer \(layerIdentifier) found.")

            guard let mapStyle = mapView.style else { return }

            // remove layer first
            if let styleLayer = mapStyle.layer(withIdentifier: layerIdentifier) {
                mapStyle.removeLayer(styleLayer)
            }

            // then remove the source
            if let source = mapStyle.source(withIdentifier: layerIdentifier) {

                mapStyle.removeSource(source)
            }
        }
        else{

            print("No layer with the identifier \(layerIdentifier) found.")
        }
    }
Was this page helpful?
0 / 5 - 0 ratings