Skip to main content

Animate marker position

Animate updates to a marker/annotation's position.
AnimatedMarkerExample.swift
import Foundation
import MapboxMaps
import UIKit

final class ViewController: UIViewController {
private var mapView: MapView!
private var currentPosition = CLLocationCoordinate2D(latitude: 64.900932, longitude: -18.167040)
private var animationStartTimestamp: CFTimeInterval = 0
private var origin: CLLocationCoordinate2D!
private var destination: CLLocationCoordinate2D!
private var displayLink: CADisplayLink? {
didSet { oldValue?.invalidate() }
}
private var cancelables = Set<AnyCancelable>()

deinit {
displayLink?.invalidate()
}

override func viewDidLoad() {
super.viewDidLoad()

let cameraOptions = CameraOptions(center: currentPosition, zoom: 5)
let initOptions = MapInitOptions(cameraOptions: cameraOptions)
mapView = MapView(frame: view.bounds, mapInitOptions: initOptions)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(mapView)

// Allows the delegate to receive information about map events.
mapView.mapboxMap.onMapLoaded.observeNext { [weak self] _ in

// Set up the example
self?.setupExample()



}.store(in: &cancelables)

let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .white
label.text = "Tap anywhere on the map"

mapView.addSubview(label)

NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 32),
label.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])

mapView.mapboxMap.loadStyle(.satelliteStreets)

// add a tap gesture handler that will allow the marker to be animated
mapView.gestures.onMapTap.observe {[weak self] context in
self?.updatePosition(newCoordinate: context.coordinate)
}.store(in: &cancelables)
}

private func setupExample() {
try? mapView.mapboxMap.addImage(UIImage(named: "dest-pin")!, id: Constants.markerIconId)

// Create a GeoJSON data source.
var source = GeoJSONSource(id: Constants.sourceId)
source.data = .feature(Feature(geometry: Point(currentPosition)))

try? mapView.mapboxMap.addSource(source)

// Create a symbol layer
var symbolLayer = SymbolLayer(id: "layer-id", source: Constants.sourceId)
symbolLayer.iconImage = .constant(.name(Constants.markerIconId))
symbolLayer.iconIgnorePlacement = .constant(true)
symbolLayer.iconAllowOverlap = .constant(true)
symbolLayer.iconOffset = .constant([0, 12])

try? mapView.mapboxMap.addLayer(symbolLayer)
}

override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)

// break reference cycle when moving away from screen
if parent == nil {
displayLink = nil
}
}

@objc private func updateFromDisplayLink(displayLink: CADisplayLink) {
let animationProgress = (CACurrentMediaTime() - animationStartTimestamp) / Constants.animationDuration

guard animationProgress <= 1 else {
displayLink.invalidate()
self.displayLink = nil
return
}

// interpolate from origin to destination according to the animation progress
let coordinate = CLLocationCoordinate2D(
latitude: origin.latitude + animationProgress * (destination.latitude - origin.latitude),
longitude: origin.longitude + animationProgress * (destination.longitude - origin.longitude)
)

// update current position
self.currentPosition = coordinate

// update source with the new marker location
self.mapView.mapboxMap.updateGeoJSONSource(withId: Constants.sourceId,
geoJSON: .feature(Feature(geometry: Point(coordinate))))

}

private func updatePosition(newCoordinate: CLLocationCoordinate2D) {
// save marker's origin and destination to interpolate between them during the animation
destination = newCoordinate
origin = currentPosition
animationStartTimestamp = CACurrentMediaTime()

// add display link
displayLink = CADisplayLink(target: self, selector: #selector(updateFromDisplayLink(displayLink:)))
displayLink?.add(to: .current, forMode: .common)
}
}

extension ViewController {
enum Constants {
static let markerIconId = "marker_icon"
static let sourceId = "source-id"
static let animationDuration: CFTimeInterval = 2
}
}
Was this example helpful?