mapbox / mapbox/mapbox-maps-ios
Layer in GeoJSONSource occasionally visibly flickers away from target location
Nobody has claimed this yet.
- Dominant language
- Swift
- Stars
- 601
- Forks
- 196
- PR merge metrics
- No merged PRs in 30d
Description
Environment
- Xcode version: 26.0 (17A324)
- iOS version: 26.0
- Devices affected: Simulator, iPhone 12 mini
- Maps SDK Version: 11.16.0
Observed behavior and steps to reproduce
- Place the component below in a new project:
ViewController.swift
import UIKit
import MapboxMaps
class ViewController: UIViewController {
private var startDistance: Double = 0
private var currrentDistance: Double = 0
private var targetDistance: Double = 0
private var startTimestamp: CFTimeInterval = 0
private var displayLink: CADisplayLink? {
didSet { oldValue?.invalidate() }
}
private var cancelables = Set<AnyCancelable>()
private var mapView: MapView!
private var mapCenterAnimator: BasicCameraAnimator?
private var mapPanAnimator: BasicCameraAnimator?
private let line = LineString(
[
.init(latitude: 42.36360, longitude: -71.05085),
.init(latitude: 41.81250, longitude: -71.42216),
]
)
private let duration: Double = 1
deinit {
displayLink?.invalidate()
}
override func viewDidLoad() {
super.viewDidLoad()
mapView = MapView(frame: view.bounds, mapInitOptions: .init())
mapView.mapboxMap.styleURI = .standard
mapView.mapboxMap.onStyleLoaded.observeNext { _ in
var source = GeoJSONSource(id: "source-point")
source.data = .geometry(.point(.init(self.line.coordinates[0])))
try! self.mapView.mapboxMap.addSource(source)
var circleOuterLayer = CircleLayer(id: "layer-circle-outer", source: "source-point")
circleOuterLayer.circleRadius = .constant(15)
circleOuterLayer.circleColor = .constant(.init(hue: 1, saturation: 1, lightness: 1)!)
try! self.mapView.mapboxMap.addLayer(circleOuterLayer)
var circleInnerLayer = CircleLayer(id: "layer-circle-inner", source: "source-point")
circleInnerLayer.circleRadius = .constant(12)
circleInnerLayer.circleColor = .constant(.init(hue: 0.7, saturation: 0.8, lightness: 0.5)!)
try! self.mapView.mapboxMap.addLayer(circleInnerLayer)
}.store(in: &cancelables)
self.view.addSubview(mapView)
Timer.scheduledTimer(withTimeInterval: duration, repeats: true) { _ in
self.move()
}
}
private func move() {
startTimestamp = CACurrentMediaTime()
startDistance = currrentDistance
targetDistance += 60
let currentPoint = line.coordinateFromStart(distance: targetDistance)!
let bounds = CoordinateBounds(
southwest: currentPoint.coordinate(at: 200, facing: 45),
northeast: currentPoint.coordinate(at: 200, facing: 180 + 45),
)
var cameraOptions: CameraOptions
do {
cameraOptions = try mapView.mapboxMap.camera(
for: [bounds.northeast, bounds.southwest],
camera: .init(
padding: .zero,
bearing: currentPoint.direction(to: line.coordinateFromStart(distance: targetDistance + 100)!),
pitch: 60
),
coordinatesPadding: nil,
maxZoom: nil,
offset: nil
)
} catch {
print("MapView | Error setting camera: \(error)")
return
}
mapCenterAnimator?.stopAnimation()
mapCenterAnimator = mapView.camera.makeAnimator(duration: duration, curve: .linear) { transition in
transition.center.toValue = bounds.center
}
mapCenterAnimator!.startAnimation()
mapPanAnimator?.stopAnimation()
mapPanAnimator = mapView.camera.makeAnimator(duration: duration, curve: .linear) { transition in
transition.zoom.toValue = cameraOptions.zoom
transition.bearing.toValue = cameraOptions.bearing
transition.pitch.toValue = cameraOptions.pitch
}
mapPanAnimator!.startAnimation()
displayLink = CADisplayLink(target: self, selector: #selector(updateFromDisplayLink(displayLink:)))
displayLink?.add(to: .current, forMode: .common)
}
@objc private func updateFromDisplayLink(displayLink: CADisplayLink) {
let progressRatio = (CACurrentMediaTime() - startTimestamp) / duration
guard progressRatio <= 1 else {
displayLink.invalidate()
self.displayLink = nil
return
}
currrentDistance = startDistance + (targetDistance - startDistance) * progressRatio
let point = line.coordinateFromStart(distance: currrentDistance)!
mapView.mapboxMap.updateGeoJSONSource(
withId: "source-point",
geoJSON: .geometry(.point(.init(point)))
)
}
}
- Build and run the project.
Expected behavior
The dot should smoothly slide along with the map.
Notes / preliminary analysis
Instead, the dot occasionally flickers off of its intended coordinates. See video, or an example normal/bad frame comparison:
It appears that the "bad" frame shows the normal layer joined with a slightly offset version of the layer.
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 by reproducing the issue from the ViewController.swift example, focusing on move(), updateFromDisplayLink(displayLink:), and updateGeoJSONSource. Compare normal and flickering frames while the camera and GeoJSON point animate; done means the dot remains at its intended coordinates throughout the animation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, swift
- Domain
- mobile-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100