mapbox / mapbox/mapbox-maps-ios
FeatureCollection blinking during the update
Nobody has claimed this yet.
- Dominant language
- Swift
- Stars
- 601
- Forks
- 196
- PR merge metrics
- No merged PRs in 30d
Description
## Environment
- Xcode version: 14.2
- iOS version: 16.1
- Devices affected: all
- Maps SDK Version: 10.10.0
## Observed behavior and steps to reproduce
Whenever I have to update a `FeatureCollection` consisting of `Point`s it makes all the items blink, not the ones that were updated.
In the Mapbox 6 it wasn't the case only the affected items updated their appearance.
New version behavior:
https://user-images.githubusercontent.com/7995896/207573740-0a46213b-18dc-409c-8526-2f13a9be4d70.mp4
Old version behavior:
https://user-images.githubusercontent.com/7995896/207575356-a02dac77-e14e-462e-bae9-28ce7b5c6e9d.mp4
To create such a collection I use the following code in the new version of Mapbox:
```Swift
let feature = FeatureCollection(
features: steps
.map { step in
let point = Point(step.coordinate)
let geometry: Geometry = .point(point)
var feature = Feature(geometry: geometry)
feature.setAttributes([
.type: .string(MapFeatureType.step.rawValue),
.uuid: .string(step.uuid),
.isSelected: .boolean(highlightedUUID == step.uuid),
.hasImage: .boolean(true)
feature[.imageURL] = .string(step.resource.downloadURL.absoluteString)
])
return feature
}
)
```
and then I add it like this:
```Swift
func ensureFeatureCollectionAdded(featureCollection: FeatureCollection, sourceIdentifier: String, clusteringOptions: ClusteringOptions? = nil, to map: PSBaseMapView) {
if let existingSource = try? map.mapboxMap.style.source(withId: sourceIdentifier) {
do {
print("ℹ️ Try to update \(sourceIdentifier) with feature collection")
try map.mapboxMap.style.updateGeoJSONSource(withId: sourceIdentifier, geoJSON: featureCollection.geoJSONObject)
print("✅ Updated \(sourceIdentifier) with feature collection")
} catch {
Logger.shared.info("❌ Could not update \(sourceIdentifier); \(error)")
}
} else {
var source = GeoJSONSource()
source.promoteId = .string(sourceIdentifier)
source.data = GeoJSONSourceData.featureCollection(featureCollection)
if let clusteringOptions = clusteringOptions {
source.cluster = true
source.clusterRadius = clusteringOptions.clusterRadius
source.clusterProperties = clusteringOptions.clusterProperties
}
do {
print("ℹ️ Try to add \(sourceIdentifier) source")
try map.mapboxMap.style.addSource(source, id: sourceIdentifier)
print("✅ Added \(sourceIdentifier) source")
} catch {
Logger.shared.info("❌ Could not add \(sourceIdentifier); \(error)")
}
}
}
```
So, the blinking happens on this stage:
`print("✅ Updated \(sourceIdentifier) with feature collection")`
It seems that the whole FeatureCollection gets invalidated and redrawn.
------------------------------------------------------------------------
In the previous version of Mapbox I would achieve the desired result in the following manner:
I would have this property:
```Swift
private var stepsSource: MGLShapeSource? {
map?.geoJSONSource(withIdentifier: "My ID")
}
```
then I would create a `FeatureCollection` in the same manner as abovementioned, and then to update the view I would call:
`stepsSource.shape = feature.mglShape`
where `mglShape` is:
```Swift
extension GeoJSONObjectConvertible {
var mglShape: MGLShape? {
do {
let data = try JSONEncoder().encode(self.geoJSONObject)
let shape = try MGLShape(data: data, encoding: String.Encoding.utf8.rawValue)
return shape
} catch {
logger.warning("Couldn't encode geoJSONObject to shape: \(error.localizedDescription)")
return nil
}
}
}
```
## Expected behavior
Only the changed pieces of FeatureCollection get redrawn or at least not all of the points blink.
## Notes / preliminary analysis
At first, I thought of reworking it to annotations to be able to update only the affected items manually by ID, but then I read in your docs saying that the performance of annotations is good up until there are 100-200 annotations on a map at a time. In our case, it's not gonna be sufficient, because at Polarsteps we often deal with world trips having hundreds of steps.
Is there anything that you might help us with on this one? We first tried to upgrade the lib earlier this year, but there were some performance issues. Nowadays it seems they are gone (bravo!). But this blinking issue makes the whole UX worse for our audience.
P.S. If I remove `.isSelected: .boolean(highlightedUUID == step.uuid)` from Feature collection creation function, then the issue is gone. So, it has something to do with the whole Feature collection getting invalidated and redrawn even though only 2 items changed.
And if I try and compare features one by one, I can tell that only two of them change (the one unselected and a newly selected one, for the others I get at least expected `Equatable` results).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the reported updateGeoJSONSource call in ensureFeatureCollectionAdded and the promoteId setup, then compare behavior when the isSelected attribute is omitted. Trace how FeatureCollection updates are rendered and verify the expected behavior against the provided reproduction: changing selection should redraw only the affected points, without blinking the entire collection.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, swift
- Domain
- mobile, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100