Showing the user's location in a Mapbox MapView

Showing the user's location in a Mapbox MapView

With this article you will learn how to obtain the user's location and show it on a map using the Mapbox SDK for iOS.

In recent articles, we explored how to integrate Mapbox within a SwiftUI app. The powerful Mapbox SDK for iOS natively supports UIKit, but it can conveniently be integrated with SwiftUI, using UIViewControllerRepresentable to wrap the UIViewController in a SwiftUI compatible view. We also explored how to add annotations to add annotaitons to a Mapbox map. Now, let's focus on showing the user's location on the map.

To enable this functionality, we can leverage the built in features of the Mapbox Maps SDK that enables the app to observe and respond the user's location. Following iOS privacy features, the user must grant the application permission before the location information can be access. For this, the user is presented with a permission prompt that should explain how the information will be used in the app. This process is configured by adding a NSLocationWhenInUseUsageDescription key to the Info.plist file. As a value, a meaningful description why the app is requesting this permission can be added.

Ever since iOS 14, users are able to share only reduced-accurcay locations, which is the accuracy level developers are encouraged to use by Apple unless specific needs require more precise acces. There are use cases for more precise location, for exmaple navigation features. To learn more about how precice location can be acquired in your app, check out the Mapbox Map SDK for iOS documentation.

The following example shows a MapBoxMapView struct that abides to the UIViewControllerRepresentable protocol to allow the custom MapViewcontroller to be used inside a SwiftUI view hierarchy. The view controller establishes the Mapbox functionality as the SDK only supports UIKit. To learn more about the initial configuration of the MapView, refer to our article on how to integrate Mapbox with SwiftUI.

The relevant lines of code for the purpose of showing the user's location are referring to the mapView.location. Most relevantly, the location.delegate has to be set to self to allow the view controller to manage to location updates. For this, also the LocationsPersmissionsDelegate protocol has to be added to the MapViewController. Then, through the location.options, the functionality can be further defined, e.g. through the .activityType that can destinguish between fitness activity, nagivation etc. The .puckType can be set to either a 2D or 3D default puck, but it can also be customised completely if desired. Lastly, the location.locationProvider.startUpdatingLocation() initiates the regular request of the user's location. It can also be stopped using the location.locationProvider.stopUpdatingLocation().

import SwiftUI
import MapboxMaps

struct MapBoxMapView: UIViewControllerRepresentable {
     
    func makeUIViewController(context: Context) -> MapViewController { 
           return MapViewController()
       }
      
    func updateUIViewController(_ uiViewController: MapViewController, context: Context) {
    }
}


class MapViewController: UIViewController {
   internal var mapView: MapView!

   override public func viewDidLoad() {
       super.viewDidLoad()
       let myResourceOptions = ResourceOptions(accessToken: "YOUR-ACCESS-TOKEN")
       let cameraOptions = CameraOptions(center: CLLocationCoordinate2D(latitude: 40.83647410051574, longitude: 14.30582273457794), zoom: 4.5)
       let myMapInitOptions = MapInitOptions(resourceOptions: myResourceOptions, cameraOptions: cameraOptions, styleURI: StyleURI.dark)
       mapView = MapView(frame: view.bounds, mapInitOptions: myMapInitOptions)
       mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
       self.view.addSubview(mapView)
       
       mapView.location.delegate = self
       mapView.location.options.activityType = .other
       mapView.location.options.puckType = .puck2D()
       mapView.location.locationProvider.startUpdatingLocation()
       
   }
}

extension MapViewController: LocationPermissionsDelegate {
    
}

Using the location data in the app

Beyond showing the location on the map, the location data can of course also be used to shape the behaviour of the map. For example, the camera of the map could animate to the user's location based on certain interaction, for example when touching a button or when the map is opened the first time.

In this simplified example, the .onNext closure of the map is used to update the location once the map is loaded. This is done mainly since the initial user's location takes some time to be acquired and would be nil if the .locationUpdate is called direclty in viewDidLoad(). In a real use case scenario this has to be adjusted to something meaningful, like a specific user interaction etc.

However, the example shows how the LocationConsumer protocol can be used with its func locationUpdate(newLocation: Location). Any code that should be executed when the location update is received can be setup there. In this case, we use mapView.camera.fly to animate the map to fly to the user's location a specific zoom level of 14.0 wihin a defined timeframe of 5 seconds.

class MapViewController: UIViewController {
   internal var mapView: MapView!

   override public func viewDidLoad() {
       super.viewDidLoad()
       
       [...]
       
       mapView.location.delegate = self
       mapView.location.options.activityType = .other
       mapView.location.options.puckType = .puck2D()
       mapView.location.locationProvider.startUpdatingLocation()
       
       mapView.mapboxMap.onNext(.mapLoaded) { [self]_ in
           self.locationUpdate(newLocation: mapView.location.latestLocation!)
       }
   }
}

extension MapViewController: LocationPermissionsDelegate, LocationConsumer {
    func locationUpdate(newLocation: Location) {
        mapView.camera.fly(to: CameraOptions(center: newLocation.coordinate, zoom: 14.0), duration: 5.0)
    }
}

The MapBoxMapView can then be used inside any SwiftUI view hierarchy, for example inside the default ContentView.swift file that comes with any new SwiftUI project in Xcode. Upon launch, the map is loaded, then the user's location is acquired and the map flys to the latest user's position.

import SwiftUI

struct ContentView: View {
    var body: some View {
        MapBoxMapView()
            .ignoresSafeArea()
    }
}

Stay tuned for more content on how to use maps in your SwiftUI apps. We will cover more in-depth features of MapKit as well as Mapbox, how to make use of Core Location to show the user's location on the map, and other topics in the future.