Adding Map Controls to a Map view with SwiftUI and MapKit
Learn how to add map controls to a map in a SwiftUI application.
MapKit features many controls that allow the user to interact with the map easily:
MapCompass
displays the current orientation of the associated mapMapPitchToggle
sets the pitch of the associated map (the camera angle as 3D or 2D)MapScaleView
shows a legend with distance information when the user zooms in and out (requires the elevation to be set as realistic)MapUserLocationButton
frames the map to the user's current location (requires authorization to use the user's location)
Some controls are available only for macOS though, replacing actions that on iPhone and iPad the users do with gestures, like:
MapPitchSlider
allows the user to change the pitch of the map (macOS only)MapZoomStepper
allows the user to adjust the zoom level(macOS only)
To add these controls to a Map
view on SwiftUI you can use a modifier or use them as standalone views that share the same scope of the Map.
Let’s use the following code as a starting point:
import SwiftUI
import MapKit
struct ContentView: View {
let garage = CLLocationCoordinate2D(
latitude: 40.83657722488077,
longitude: 14.306896671048852
)
@State private var position: MapCameraPosition = .userLocation(fallback: .automatic)
var body: some View {
Map(position: $position) {
Marker("Garage", coordinate: garage)
}
.mapStyle(.standard(elevation: .realistic))
}
}
It creates a Map
view with an annotation marker, a position for the map camera, and sets up the elevation to be realistic rendering the map in 3D. A map style is initialized with a MapStyle
object.
Be aware that to access the user's location, authorization has to be requested for privacy reasons. This requires editing the Info section of your project settings and implementing a CoreLocation manager to handle the requests and updates for the user's location.
Read more about it in the following article at the official Apple documentation.
Using the mapControls(_:)
modifier
The first way of adding controls to a Map view is by using the mapControls(_:)
modifier. It allows you to define the controls that will appear on top of the map without having to specify a scope for them.
Map(position: $position) {
Marker("Garage", coordinate: garage)
}
.mapControls {
/// Shows up when you pitch to zoom
MapScaleView()
/// Shows up when you rotate the map
MapCompass()
/// 3D and 2D button on the top right
MapPitchToggle()
}
.mapStyle(.standard(elevation: .realistic))
Using map controls as views
You can also use the map controls as views in a view hierarchy, but then you need to associate them with a map via a scope. To achieve it you need to:
- Define a namespace in your view
- Set it up using the
mapScope(_:)
modifier - Associate the scope to the
Map
view - Connect the map controls you add with the map by assigning the same scope
Here is an example:
import SwiftUI
import MapKit
struct ContentView: View {
let garage = CLLocationCoordinate2D(
latitude: 40.83657722488077,
longitude: 14.306896671048852
)
@State private var position: MapCameraPosition = .userLocation(fallback: .automatic)
/// Create a namespace for the map to work at
@Namespace var mapScope
var body: some View {
ZStack(alignment: .topTrailing) {
/// Associates the scope to the map view
Map(position: $position, scope: mapScope) {
Marker("Garage", coordinate: garage)
}
.mapStyle(.standard(elevation: .realistic))
/// Adds a control and connects it to the map scope
MapPitchToggle(scope: mapScope)
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.white)
)
.shadow(radius: 10)
.padding()
}
/// Creates a map scope to connect map controls to an associated map
.mapScope(mapScope)
}
}