mapbox-maps-ios icon indicating copy to clipboard operation
mapbox-maps-ios copied to clipboard

Updating an image in style - some features in SymbolLayer are not redrawn on current zoom level

Open f3dm76 opened this issue 1 year ago • 2 comments

Environment

  • Xcode version: 15.0.1
  • iOS version: 17.0.1
  • Devices affected: iphone 15 pro max simulator and real device iphone 12 mini
  • Maps SDK Version: MapboxMaps 11.1.0

Observed behavior and steps to reproduce

I’m writing a SwiftUI app using mapbox to display a number of pins. I’m using a SymbolLayer with FeatureCollection to display these pins. Each pin should display a remote URL image. But while remote images are being loaded I’m showing a standard pin image from local assets.

let imageID = "image\(event.id)"
try! map.addImage(pinImage, id: imageID)
feature.properties = ["image": JSONValue(imageID)]

/// and later in layer creation I set up the pin to use this image
pinLayer.iconImage = .expression(Exp(.get) { "image" })

Once any of the remotes are loaded I’m updating the image with pin’s id like this:

try! map.addImage(loadedImage, id: imageID)

Mapbox AI told me that this will replace the image with the new one and that all of the layers referencing this image should be redrawn to use this new picture. However what’s happening is that for current zoom level the old simple pin is still showing. If I zoom in/out new images are there, but for current zoom nothing changes. I tried force updating the map and the layer like this:

try? style.updateLayer(withId: pinLayerID, type: SymbolLayer.self) {_ in}
self.mapView.mapboxMap.triggerRepaint()

But nothing helps. Here is the whole relative code. the MapView creation:

class MapViewController: UIViewController {

    var events: [Event]

    internal var mapView: MapView!

    var cancelables = Set<AnyCancelable>()

    var map: MapboxMap {
        mapView.mapboxMap
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override public func viewDidLoad() {
        super.viewDidLoad()
        if let token = Bundle.main.object(forInfoDictionaryKey: "MBXAccessToken") as? String {
            let center = CLLocationCoordinate2D(latitude: 43.68563039075405, longitude: -79.30424301014425)
            let cameraOptions = CameraOptions(center: center, zoom: 11)
            MapboxOptions.accessToken = token
            let myMapInitOptions = MapInitOptions(mapOptions: MapOptions(), cameraOptions: cameraOptions, styleURI: .dark)

            mapView = MapView(frame: view.bounds, mapInitOptions: myMapInitOptions)
            mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            self.view.addSubview(mapView)

            mapView.mapboxMap.onMapLoaded.observeNext { _ in
// add simple local pins
                self.addSymbolClusteringLayers()
            }.store(in: &cancelables)

            mapView.mapboxMap.onMapIdle.observeNext { _ in
// load remote images for all the pins and replace the old images with the new
                self.loadRemotePinImages()
            }.store(in: &cancelables)
        }
    }
}

pins creation:

extension MapViewController {
    func addSymbolClusteringLayers() {
        let pinImage = UIImage(named: "pin")!

        var source = GeoJSONSource(id: sourceID)

        var features: [Feature] = []
        for event in self.events {
            if let coord = event.coordinate {
                var feature = Feature(geometry: Geometry.point(Point(LocationCoordinate2D(latitude: coord.latitude, longitude: coord.longitude))))
                feature.identifier = FeatureIdentifier(event.id)
                let imageID = "image\(event.id)"
// initially place simple pin image into map style for every pin
                try! map.addImage(pinImage, id: imageID)
// setup image name to use for each feature - for now they are the same pinImage for every id, but we'll replace them later
                feature.properties = ["image": JSONValue(imageID)]
                features.append(feature)
            }
        }

        let featureCollection = FeatureCollection(features: features)
        let data = GeoJSONSourceData.featureCollection(featureCollection)
        source.data = data

        let pinLayer = SymbolLayer(id: pinLayerID, source: sourceID)
// take the image name we set up earlier. the image name will stay the same, but the image itself will be changed
        pinLayer.iconImage = .expression(Exp(.get) { "image" })

        try! map.addSource(source)
        try! map.addLayer(pinLayer)
    }

    func loadRemotePinImages() {
        Task.detached(priority: .background) {
            await self.events.asyncForEach { event in
                await self.loadRemotePinImage(event: event)
            }
        }
    }

    private func loadRemotePinImage(event: Event) async {
        Task.detached(priority: .background) {
            if let pinImage = await EventPinsCacheManager.shared.fetchPinImage(event: event) {
                DispatchQueue.main.async {
// when the remote image is loaded - replace simple pin image with the new one for every image id. this should automatically update each feature in our symbol layer
                    try! self.map.addImage(pinImage, id: "image\(event.id)")
                }
            }
        }
    }
}

fetchPinImage basically does this:

let imageData = try? Data(contentsOf: imageUrl)
return UIImage(data: imageData)

and forms a nice pin image out of it

Expected behavior

I'd expect the pins on the map to be updated as soon as I call try! map.addImage(loadedImage, id: imageID) for respective image

Notes / preliminary analysis

It really looks like a bug since it works for other zoom levels, it feels like I just need to call some force update mechanism and it will fix itself

Additional links and references

https://github.com/mapbox/mapbox-maps-ios/assets/9447630/22b88ff8-b480-4cad-bf12-c99e9f8bc585

f3dm76 avatar Jan 22 '24 12:01 f3dm76

https://github.com/mapbox/mapbox-maps-ios/issues/2093

fawzirifai avatar Feb 01 '24 22:02 fawzirifai

Internal issue: https://mapbox.atlassian.net/browse/MAPSNAT-1768

baleboy avatar Feb 02 '24 14:02 baleboy