User location
The Mapbox Maps SDK for iOS enables your application to observe and respond to the user's location. It helps you request permission to access a user's location, use a location provider to get location information for the device, and display the user's location on the map visually.
Privacy and permissions
Users must grant your application permission before it can access information about their location. During this permission prompt, a custom string may be presented explaining how location will be used. This is specified by adding the key NSLocationWhenInUseUsageDescription
to the Info.plist
file with a value that describes why the application is requesting these permissions.
Before iOS 14, the device could only send the user’s exact location. With iOS 14, users can opt into only sharing reduced-accuracy locations. Since users may toggle full-accuracy location off when initial permission for their location is requested by the app or in the System settings, developers are strongly encouraged to support reduced-accuracy locations.
Request temporary access to full-accuracy location
Certain application features may require full-accuracy locations. The Mapbox Maps SDK for iOS provides a wrapper of Apple’s Core Location APIs that requests temporary access to full-accuracy locations when the user has opted out.
Make the following adjustments to your Info.plist
file to enable these prompts and provide explanations to appear within them:
- Provide users with a brief explanation of how the app will use their location data for temporary access:
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your precise location is used to calculate turn-by-turn directions, show your location on the map, and help improve the map.</string> - Add
LocationAccuracyAuthorizationDescription
as an element of theNSLocationTemporaryUsageDescriptionDictionary
dictionary to give users a brief explanation of why a feature in your app requires their exact location:<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
<key>LocationAccuracyAuthorizationDescription</key>
<string>Please enable precise location. Turn-by-turn directions only work when precise location data is available.</string>
</dict>
Handle changes in location authorization
After the current session elapses, your users will be prompted to give location permissions the next time they open your app. Your users must enable full-accuracy location during this prompt or in the app’s system settings to avoid being asked repeatedly for location accuracy permissions.
Customize accuracy authorization handling
If you want to customize how accuracy authorization updates are handled, use the location permission delegate. The AppleLocationProviderDelegate
protocol has a set of delegate methods that will be called when the user makes changes to location permissions.
class ViewController: UIViewController {
// This controller's initialization has been omitted in this code snippet
var mapView: MapView!
// A location provider that you use to customize location settings.
let locationProvider = AppleLocationProvider()
override func viewDidLoad() {
super.viewDidLoad()
mapView = MapView()
// Override the default location provider with the custom one.
mapView.location.override(provider: locationProvider)
locationProvider.delegate = self
}
// Method that will be called as a result of the delegate below
func requestPermissionsButtonTapped() {
locationProvider.requestTemporaryFullAccuracyAuthorization(withPurposeKey: "CustomKey")
}
}
extension ViewController: AppleLocationProviderDelegate {
func appleLocationProvider(
_ locationProvider: MapboxMaps.AppleLocationProvider,
didChangeAccuracyAuthorization accuracyAuthorization: CLAccuracyAuthorization
) {
if accuracyAuthorization == .reducedAccuracy {
// Perform an action in response to the new change in accuracy
}
}
}
The code above uses on a withPurposeKey
parameter to handle a location permission change. This value must correspond to the key of a key/value pair that was specified in Info.plist
.
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
<key>CustomKey</key>
<string>We temporarily require your precise location for an optimal experience.</string>
</dict>
You should also omit the LocationAccuracyAuthorizationDescription
key from NSLocationTemporaryUsageDescriptionDictionary
so that the SDK will not show the prompt automatically.
Working with location
The LocationManager
in the Maps SDK manages location puck and the location-tracking viewport.
There are two types of location data used by the LocationManager
:
- Location data, represented by a sequence of
Location
updates. - Heading (compass) data, represented by a sequence of
Heading
updates.
The location data is used for geographical positioning, while the heading data is optional and is used to display the user's device orientation on the map.
By default, the LocationManager
creates the default instance of AppleLocationProvider
to receive the location data. You can create your own instance and use it to change the default settings or use a completely custom provider.
// Override location provider to customize settings.
let locationProvider = AppleLocationProvider()
locationProvider.options.activityType = .automotiveNavigation
mapView.location.override(provider: locationProvider)
Custom location providers
If you have a custom location source, you can supply your own data to the map.
To achieve that, implement and override the LocationProvider
and HeadingProvider
protocols:
// Set custom location and heading providers
let locationProvider: LocationProvider = //...
let headingProvider: HeadingProvider = //...
mapView.location.override(locationProvider: locationProvider, headingProvider: headingProvider)
In SwiftUI, use MapReader
and Combine publishers to override the location providers.
/// SwiftUI
struct LocationOverrideExample: View {
private class LocationProvider {
@Published var location = Location(coordinate: .zero)
@Published var heading = Heading(direction: 0, accuracy: 0)
}
@State private var provider = LocationProvider()
var body: some View {
MapReader { proxy in
Map {
Puck2D(bearing: .heading)
}
.onAppear {
/// Override the location and Heading provider with Combine publishers.
proxy.location?.override(
locationProvider: provider.$location.map { [$0] }.eraseToSignal(),
headingProvider: provider.$heading.eraseToSignal())
}
}
}
}
The eraseToSignal
in the example above converts any Publisher
to the internal Signal
, which is used internally by the Maps SDK.
When implementing a custom location providers, be sure you ask user permissions for location data.
Location puck
Enabling user location puck in LocationOptions
will render a puck on the map that shows the device's current location and handle requesting permissions and all the nuances that come with different permission levels.
Enable the default user location display using the following code:
Map {
// By default, the built-in 2D puck doesn't show the user's heading.
Puck2D()
// To display the heading, you must enable it explicitly as follows:
Puck2D(bearing: .heading)
}
// By default, the built-in 2D puck doesn't show the user's heading.
mapView.location.options.puckType = .puck2D()
// To display the heading, you must enable it explicitly as follows:
let configuration = Puck2DConfiguration.makeDefault(showBearing: true)
mapView.location.options.puckType = .puck2D(configuration)
Show the user's location on a map using the default location puck.
Set puck style options
There are several options to customize the appearance of the location puck using LocationOptions
. Using the PuckType
enum, you can set whether to use a 2D or 3D puck.
Set Puck Bearing Source
On iOS, the user location can track bearing using the device heading
or device course
. This option can be set in LocationOptions
.
Location Tracking
To make the camera follow the location puck, you can use the Follow Puck Viewport.
- In UIKit, you use
ViewportManager
accessible asMapView.viewport
property. - In SwiftUI, the
Viewport
configuration can be passed to theMap
view.
Viewport States
ViewportState
produces camera updates based on implementation-specific rules (For example tracking a dynamic location data source or showing a static overview of a predefined region).
Two ViewportState
implementations are provided by the SDK, both of which can be instantiated from Viewport
:
viewport.makeFollowPuckViewportState(options:) -> FollowPuckViewportState
: This state syncs the map camera with the location puck.viewport.makeOverviewViewportState(options:) -> OverviewViewportState
: This state makes the camera show a user-provided geometry.
Besides using these built-in implementations, you can also create your own and use them with Viewport
.
Viewport Transitions
ViewportTransition
defines how to transition to a target ViewportState
.
Two ViewportTransition
implementations also provided by the SDK, both of which can be instantiated from Viewport
:
viewport.makeDefaultViewportTransition(options:) -> DefaultViewportTransition
: The default viewport transition uses sophisticated animations to move the camera to the target state.viewport.makeImmediateViewportTransition() -> ImmediateViewportTransition
: The immediate viewport transition moves the camera to the target state immediately without using animations.
Besides using these built-in implementations, you can also create your own and use them with Viewport
.
Example
struct ViewportExample: View {
// By default, camera settings come from style settings.
@State var viewport = Viewport.styleDefault
var body: some View {
Map(viewport: $viewport)
Button("Follow puck") {
// An animated transition to the follow-puck viewport state.
withViewportAnimation {
viewport = .followPuck(zoom: 16.35, pitch: 45).padding(.top, 200)
}
}
Button("Overview") {
// An immediate transition to the overview state.
viewport = .overview(geometry: routePoints).padding(.all, 100)
}
}
}
import MapboxMaps
...
let followPuckViewportState = mapView.viewport.makeFollowPuckViewportState(
options: FollowPuckViewportStateOptions(
padding: UIEdgeInsets(top: 200, left: 0, bottom: 0, right: 0),
bearing: .constant(0),
animationDuration: 0.5))
let overviewViewportState = mapView.viewport.makeOverviewViewportState(
options: OverviewViewportStateOptions(
geometry: routePoints,
padding: UIEdgeInsets(top: 100, left: 100, bottom: 100, right: 100)))
let immediateTransition = mapView.viewport.makeImmediateViewportTransition()
// transition from idle (the default status) to the created followPuckViewportState with default transition
mapView.viewport.transition(to: followPuckViewportState) { success in
// the transition has been completed with a flag indicating whether the transition succeeded
}
...
mapView.viewport.transition(to: overviewViewportState, transition: immediateTransition) { success in
// the transition has been completed with a flag indicating whether the transition succeeded
}