Skip to main content

SwiftUI - Locate Me

This example demonstrates a SwiftUI implementation of a "Locate Me" functionality using the Mapbox Maps SDK for iOS. The LocateMeExample struct includes a Map component with a Puck2D element, allowing users to locate themselves on the map.

The user's location is tracked through a Viewport state variable, which can be adjusted to follow the user's location at varying zoom levels and bearing angles.

The LocateMeButton struct defines a button that users can interact with to toggle between different map tracking modes, enabling them to focus on their location, follow their movement, or support an idle state. The button transitions smoothly between different states using animation effects. This example showcases a user-friendly way to incorporate location tracking and map interaction in a SwiftUI environment.

Troubleshooting with the iOS simulator

If you cannot see the location puck while testing in the iOS simulator, try the following:

  • Simulate a location in Xcode by going to Debug > Simulate Location and select a location. Then, return to the Simulator and the location puck should appear over the selected location.
  • If you already declined location permissions for your application you can update permission in Settings. Go to Settings > Privacy & Security > Location Services. Then find your application and update the location access to "Always" or "While Using the App".
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.

LocateMeExample.swift
import SwiftUI
@_spi(Experimental) import MapboxMaps
import CoreLocation

/// The example demonstrates Puck and Viewport configuration that allow to follow user location.
struct LocateMeExample: View {
@State var viewport: Viewport = .followPuck(zoom: 13, bearing: .constant(0))

var body: some View {
MapReader { _ in
Map(viewport: $viewport) {
Puck2D(bearing: .heading)
.showsAccuracyRing(true)
}
.mapStyle(.standard)
.ignoresSafeArea()
.overlay(alignment: .trailing) {
LocateMeButton(viewport: $viewport)
}
}
}
}

/// The example demonstrates Puck and Viewport configuration that allow to follow user location.
/// In this example the CoreLocationProvider is use instead of default `AppleLocationProvider`.
struct LocateMeCoreLocationProviderExample: View {
@State var locaionModel = LocationDataModel.createCore()
@State var viewport: Viewport = .followPuck(zoom: 13, bearing: .constant(0))

var body: some View {
MapReader { _ in
Map(viewport: $viewport) {
Puck2D(bearing: .heading)
.showsAccuracyRing(true)

}
.mapStyle(.standard)
.locationDataModel(locaionModel)
.ignoresSafeArea()
.overlay(alignment: .trailing) {
LocateMeButton(viewport: $viewport)
}
.onAppear {
/// The core location provider doesn't automatically initiate the location authorization request.
/// Instead, the application is responsible for that.
let locationManager = CLLocationManager()
if locationManager.authorizationStatus == .notDetermined {
locationManager.requestWhenInUseAuthorization()
}
}

}
}
}

struct LocateMeButton: View {
@Binding var viewport: Viewport

var body: some View {
Button {
withViewportAnimation(.default(maxDuration: 1)) {
if isFocusingUser {
viewport = .followPuck(zoom: 16.5, bearing: .heading, pitch: 60)
} else if isFollowingUser {
viewport = .idle
} else {
viewport = .followPuck(zoom: 13, bearing: .constant(0))
}
}
} label: {
Image(systemName: imageName)
.transition(.scale.animation(.easeOut))
}
.safeContentTransition()
.buttonStyle(MapFloatingButtonStyle())
}

private var isFocusingUser: Bool {
return viewport.followPuck?.bearing == .constant(0)
}

private var isFollowingUser: Bool {
return viewport.followPuck?.bearing == .heading
}

private var imageName: String {
if isFocusingUser {
return "location.fill"
} else if isFollowingUser {
return "location.north.line.fill"
}
return "location"

}
}

private extension View {
func safeContentTransition() -> some View {
if #available(iOS 17, *) {
return self.contentTransition(.symbolEffect(.replace))
}
return self
}
}

struct LocateMeExample_Preview: PreviewProvider {
static var previews: some View {
LocateMeExample()
}
}
Was this example helpful?