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.
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.
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)
}
}