Skip to main content

Animate a line

A newer version of the Maps SDK is available

This page uses v6.4.1 of the Mapbox Maps SDK. A newer version of the SDK is available. Learn about the latest version, v11.2.0, in the Maps SDK documentation.

Related examples: drawing a GeoJSON line and dynamically-styled line.

ViewController
import Mapbox

class ViewController: UIViewController, MGLMapViewDelegate {
var mapView: MGLMapView!
var timer: Timer?
var polylineSource: MGLShapeSource?
var currentIndex = 1
var allCoordinates: [CLLocationCoordinate2D]!

override func viewDidLoad() {
super.viewDidLoad()

mapView = MGLMapView(frame: view.bounds)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

mapView.setCenter(
CLLocationCoordinate2D(latitude: 45.5076, longitude: -122.6736),
zoomLevel: 11,
animated: false)
view.addSubview(mapView)

mapView.delegate = self

allCoordinates = coordinates
}

// Wait until the map is loaded before adding to the map.
func mapViewDidFinishLoadingMap(_ mapView: MGLMapView) {
addPolyline(to: mapView.style!)
animatePolyline()
}

func addPolyline(to style: MGLStyle) {
// Add an empty MGLShapeSource, we’ll keep a reference to this and add points to this later.
let source = MGLShapeSource(identifier: "polyline", shape: nil, options: nil)
style.addSource(source)
polylineSource = source

// Add a layer to style our polyline.
let layer = MGLLineStyleLayer(identifier: "polyline", source: source)
layer.lineJoin = NSExpression(forConstantValue: "round")
layer.lineCap = NSExpression(forConstantValue: "round")
layer.lineColor = NSExpression(forConstantValue: UIColor.red)

// The line width should gradually increase based on the zoom level.
layer.lineWidth = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)",
[14: 5, 18: 20])
style.addLayer(layer)
}

func animatePolyline() {
currentIndex = 1

// Start a timer that will simulate adding points to our polyline. This could also represent coordinates being added to our polyline from another source, such as a CLLocationManagerDelegate.
timer = Timer.scheduledTimer(timeInterval: 0.05, target: self, selector: #selector(tick), userInfo: nil, repeats: true)
}

@objc func tick() {
if currentIndex > allCoordinates.count {
timer?.invalidate()
timer = nil
return
}

// Create a subarray of locations up to the current index.
let coordinates = Array(allCoordinates[0..<currentIndex])

// Update our MGLShapeSource with the current locations.
updatePolylineWithCoordinates(coordinates: coordinates)

currentIndex += 1
}

func updatePolylineWithCoordinates(coordinates: [CLLocationCoordinate2D]) {
var mutableCoordinates = coordinates

let polyline = MGLPolylineFeature(coordinates: &mutableCoordinates, count: UInt(mutableCoordinates.count))

// Updating the MGLShapeSource’s shape will have the map redraw our polyline with the current coordinates.
polylineSource?.shape = polyline
}

let coordinates = [
(-122.63748, 45.52214),
(-122.64855, 45.52218),
(-122.6545, 45.52219),
(-122.65497, 45.52196),
(-122.65631, 45.52104),
(-122.6578, 45.51935),
(-122.65867, 45.51848),
(-122.65872, 45.51293),
(-122.66576, 45.51295),
(-122.66745, 45.51252),
(-122.66813, 45.51244),
(-122.67359, 45.51385),
(-122.67415, 45.51406),
(-122.67481, 45.51484),
(-122.676, 45.51532),
(-122.68106, 45.51668),
(-122.68503, 45.50934),
(-122.68546, 45.50858),
(-122.6852, 45.50783),
(-122.68424, 45.50714),
(-122.68433, 45.50585),
(-122.68429, 45.50521),
(-122.68456, 45.50445),
(-122.68538, 45.50371),
(-122.68653, 45.50311),
(-122.68731, 45.50292),
(-122.68742, 45.50253),
(-122.6867, 45.50239),
(-122.68545, 45.5026),
(-122.68407, 45.50294),
(-122.68357, 45.50271),
(-122.68236, 45.50055),
(-122.68233, 45.49994),
(-122.68267, 45.49955),
(-122.68257, 45.49919),
(-122.68376, 45.49842),
(-122.68428, 45.49821),
(-122.68573, 45.49798),
(-122.68923, 45.49805),
(-122.68926, 45.49857),
(-122.68814, 45.49911),
(-122.68865, 45.49921),
(-122.6897, 45.49905),
(-122.69346, 45.49917),
(-122.69404, 45.49902),
(-122.69438, 45.49796),
(-122.69504, 45.49697),
(-122.69624, 45.49661),
(-122.69781, 45.4955),
(-122.69803, 45.49517),
(-122.69711, 45.49508),
(-122.69688, 45.4948),
(-122.69744, 45.49368),
(-122.69702, 45.49311),
(-122.69665, 45.49294),
(-122.69788, 45.49212),
(-122.69771, 45.49264),
(-122.69835, 45.49332),
(-122.7007, 45.49334),
(-122.70167, 45.49358),
(-122.70215, 45.49401),
(-122.70229, 45.49439),
(-122.70185, 45.49566),
(-122.70215, 45.49635),
(-122.70346, 45.49674),
(-122.70517, 45.49758),
(-122.70614, 45.49736),
(-122.70663, 45.49736),
(-122.70807, 45.49767),
(-122.70807, 45.49798),
(-122.70717, 45.49798),
(-122.70713, 45.4984),
(-122.70774, 45.49893)
].map({CLLocationCoordinate2D(latitude: $0.1, longitude: $0.0)})
}