Show your current position on the map, as well as switch between different types of auto-pan modes.
Use case
When using a map within a GIS application, it may be helpful for a user to know their own location within a map, whether that's to aid the user's navigation or to provide an easy mean of identifying/collecting geospatial information at their location.
How to use the sample
Tap the "Location Settings" button to open the settings interface.
Toggle "Show Location" to change the visibility of the location indicator in the map view. It will be asked by the system to provide permission to use the device's location, if the user have not yet used location services in this app.
Change the "Auto-Pan Mode" to choose if and how the SDK will position the map view's viewpoint to keep the location indicator in-frame. A menu will appear with the following options to change the LocationDisplay.AutoPanMode
:
- Off: Starts the location display with no auto-pan mode set.
- Re-Center: Starts the location display with auto-pan mode set to
recenter
. - Navigation: Starts the location display with auto-pan mode set to
navigation
. - Compass Navigation: Starts the location display with auto-pan mode set to
compassNavigation
.
How it works
- Create a
LocationDisplay
object with a location data source. - Use the
locationDisplay(_:)
map view modifier to set the location display for the map view. - Use the
LocationDisplay.AutoPanMode
property to change how the map behaves when location updates are received. - Use the
start()
andstop()
methods on the location display's data source as necessary.
Relevant API
- LocationDataSource
- LocationDisplay
- LocationDisplay.AutoPanMode
- MapView
- MapView.locationDisplay(_:)
Additional information
Location permissions are required for this sample.
Note: The default location data source, SystemLocationDataSource
, needs the app to be authorized in order to access the device's location. The app must contain appropriate purpose strings (NSLocationWhenInUseUsageDescription
, or NSLocationAlwaysAndWhenInUseUsageDescription
keys) along with a brief description of how you use location services in the project's Info tab.
Please read the documentation below for further details.
Tags
compass, GPS, location, map, mobile, navigation
Sample Code
// Copyright 2022 Esri
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import ArcGIS
import CoreLocation
import SwiftUI
struct ShowDeviceLocationView: View {
/// The error shown in the error alert.
@State private var error: Error?
/// A Boolean value indicating whether the settings button is disabled.
@State private var settingsButtonIsDisabled = true
/// The view model for this sample.
@StateObject private var model = Model()
var body: some View {
MapView(map: model.map)
.locationDisplay(model.locationDisplay)
.task {
guard model.locationDisplay.dataSource.status != .started else {
return
}
do {
try await model.startLocationDataSource()
settingsButtonIsDisabled = false
// Updates the current auto-pan mode if it does not match the
// location display's auto-pan mode.
for await mode in model.locationDisplay.$autoPanMode {
if model.autoPanMode != mode {
model.autoPanMode = mode
}
}
} catch {
// Shows an alert with an error if starting the data source fails.
self.error = error
}
}
.onDisappear {
model.stopLocationDataSource()
}
.toolbar {
ToolbarItem(placement: .bottomBar) {
Menu("Location Settings") {
Toggle("Show Location", isOn: $model.isShowingLocation)
Picker("Auto-Pan Mode", selection: $model.autoPanMode) {
ForEach(LocationDisplay.AutoPanMode.allCases, id: \.self) { mode in
Label(mode.label, image: mode.imageName)
.imageScale(.large)
}
}
}
.disabled(settingsButtonIsDisabled)
}
}
.errorAlert(presentingError: $error)
}
}
private extension ShowDeviceLocationView {
/// The model used to store the geo model and other expensive objects
/// used in this view.
@MainActor
class Model: ObservableObject {
/// A Boolean value indicating whether to show the device location.
@Published var isShowingLocation: Bool {
didSet {
locationDisplay.showsLocation = isShowingLocation
}
}
/// The current auto-pan mode.
@Published var autoPanMode: LocationDisplay.AutoPanMode {
didSet {
locationDisplay.autoPanMode = autoPanMode
}
}
/// A map with a standard imagery basemap style.
let map = Map(basemapStyle: .arcGISImageryStandard)
/// A location display using the system location data source.
let locationDisplay: LocationDisplay
init() {
let locationDisplay = LocationDisplay(dataSource: SystemLocationDataSource())
self.locationDisplay = locationDisplay
self.isShowingLocation = locationDisplay.showsLocation
self.autoPanMode = locationDisplay.autoPanMode
}
/// Starts the location data source.
func startLocationDataSource() async throws {
// Requests location permission if it has not yet been determined.
let locationManager = CLLocationManager()
if locationManager.authorizationStatus == .notDetermined {
locationManager.requestWhenInUseAuthorization()
}
// Starts the location display data source.
try await locationDisplay.dataSource.start()
}
/// Stops the location data source.
func stopLocationDataSource() {
Task {
await locationDisplay.dataSource.stop()
}
}
}
}
private extension LocationDisplay.AutoPanMode {
/// A human-readable label for each auto-pan mode.
var label: String {
switch self {
case .off: return "Auto-Pan Off"
case .recenter: return "Recenter"
case .navigation: return "Navigation"
case .compassNavigation: return "Compass Navigation"
@unknown default: return "Unknown"
}
}
/// The image name for each auto-pan mode.
var imageName: String {
switch self {
case .off: return "LocationDisplayOffIcon"
case .recenter: return "LocationDisplayDefaultIcon"
case .navigation: return "LocationDisplayNavigationIcon"
case .compassNavigation: return "LocationDisplayHeadingIcon"
@unknown default: return "LocationDisplayOffIcon"
}
}
}
#Preview {
NavigationStack {
ShowDeviceLocationView()
}
}