Skip to main content

Combine location

This example demonstrates using the Combine framework to control the location and heading of the Puck on a map. It uses Combine publishers to update the Puck's location and heading. The map displays using the MapView class from the Mapbox Maps SDK for iOS, with custom location and heading providers set using Combine's eraseToSignal method.

The Puck's appearance and behavior are configured through options like puckType, puckBearing, and puckBearingEnabled. Additionally, the viewport is configured to continuously follow the Puck's location, enabling seamless tracking. Users can interact with the map by tapping on it, which changes the Puck's position based on the tap location, calculating and setting the heading.

iOS Examples App Available

This example code is part of the Maps SDK for iOS Examples App, a working iOS project available on Github. iOS developers are encouraged to run the examples app locally to interact with this example in an emulator and explore other features of the Maps SDK.

See our Run the Maps SDK for iOS Examples App tutorial for step-by-step instructions.

CombineLocationExample.swift
import UIKit
import MapboxMaps
import Combine
import Turf

/// This examples shows how to use Combine framework to drive the Puck's location and heading.
@available(iOS 13.0, *)
final class ViewController: UIViewController {
@Published
private var location = Location(coordinate: CLLocationCoordinate2D(latitude: 60.17195694011002, longitude: 24.945389069265598))
@Published
private var heading = Heading(direction: 0, accuracy: 0)

private var mapView: MapView!

override func viewDidLoad() {
super.viewDidLoad()

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

// Set the custom location & heading providers
mapView.location.override(
locationProvider: $location.map { [$0] }.eraseToSignal(),
headingProvider: $heading.eraseToSignal())

// Enable the location puck
mapView.location.options = LocationOptions(
puckType: .puck2D(.makeDefault(showBearing: true)),
puckBearing: .heading,
puckBearingEnabled: true
)

// Set up the follow-puck viewport, so camera will always be focused on the puck
mapView.viewport.transition(
to: mapView.viewport.makeFollowPuckViewportState(options: .init(zoom: 16, bearing: .constant(0), pitch: 0)),
transition: mapView.viewport.makeImmediateViewportTransition())

mapView.gestures.singleTapGestureRecognizer.addTarget(self, action: #selector(onTap))

let guideLabel = UILabel()
guideLabel.text = "Tap anywhere on the map to place the puck"
guideLabel.backgroundColor = .systemBackground
guideLabel.translatesAutoresizingMaskIntoConstraints = false

view.addSubview(guideLabel)
NSLayoutConstraint.activate([
guideLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -30),
guideLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}

@objc func onTap(_ gesture: UIGestureRecognizer) {
// Place the puck to the tap location
let point = gesture.location(in: mapView)
let coordinate = mapView.mapboxMap.coordinate(for: point)

// Calculating the angle between the current coordinate, and the target coordinate
let headingDirection = location.coordinate.direction(to: coordinate)

location = Location(coordinate: coordinate)
heading = Heading(direction: headingDirection, accuracy: 0)
}


}
Was this example helpful?