If you’re only interested in the code to show a user location on the map a plain text code listing is at the end of this post...
I’ve been rethinking my approach to maps in some of my apps lately and I’m taking another pass at trying to use SwiftUI and the new MapKit in lieu of wrapping the UIKit MapKit views in UIViewRepresentable. I’ve implemented a MKMapView wrapper a few times now and I find it inelegant amongst all the concise SwiftUI code.
I understand that if I need the features that aren’t currently available via SwiftUI I need to use the wrapper but that’s a big part of my rethink. Do I really need all the features I’ve been considering? I have a tendency to over scope projects and make shipping harder than needed.
I sat down to watch the Meet MapKit for SwiftUI video again. I’ve already watched this video a handful of times. It covers a great deal of ground and explains the new features pretty well.
I want the ability to show the users location on the map. According to the video its a single line of code:
Not so fast. In the video this displays the users location and enables other features using this location.
If you’ve used the old MapKit you may recall needing to get the users permission before being given access to their location. In order to do that you had to rely on the CoreLocation framework. The WWDC video makes no mention of any of this but it’s still required.
Just like in the UIKit based MapKit you need to edit your Info.plist to provide a string explaining why you need to know the users location. You also need to rely on CoreLocation's LocationManager class in order to prompt the user to allow access to their location and keep track if permission has been granted.
In your apps Info.plist add an entry for one of the privacy requests based on your need. For example, if you only need the users location while they are in the app add “Privacy - Location When in Use Usage Description” with a string that will be shown to the user when asking permission. Anything will work here but keep in mind when shipping your app that Apple might review what you ask for here. I’ve had no problems being blunt. Something like “Displaying Users Location on the Map” is fine.
Note there are several privacy fields referencing location use. Make sure you select the correct one and your code matches the type of usage you need. Selecting the wrong permission in the Info.plist can cause user location to not work and fail silently with no indication of the error.
Once you have edited the plist you need to add an instance of CLLocationManager to your view and be sure you’ve asked for permission. This is the minimum I’ve found to display the users location:
Note that this code is asking only for permission to see the users location while they are using the app. If you require background usage, adjust both Info.plist and this code to request what you need.
By default a preview view will show the map zoomed out to show all of North America with the user located at Apple Park in Cupertino, CA.
One other new feature of MapKit for SwiftUI is the ability to add additional map controls to the view such as the map scale, compass or a button to zoom to the user location. The screenshot above shows the MapUserLocationButton in its default location. You may not need this in your apps but I find this button very handy, even if only during debugging, to quickly check that things are working so update the code to add a mapControls modifier so it looks like this:
In my initial experiments with getting user location displayed it seemed all that was needed was to ask if the user has given permission and if not ask. I tried various ways of asking for permission and settled on what is shown here. If you play around with this you may wonder why the @State is required to store the CLLocationManager instance at all. It is needed so that the map can update the view when the users location changes. If you omit the @State for the CLLocationManager and try something like this:
you will see the users position on the map with the familiar blue dot BUT it will not update if and when the user moves. The new SwiftUI implementation of MapKit gives you easy user location tracking but only if you provide a @State to hold the instance of the CLLocationManager.
Thus far I have been unable to get the preview views to show any other locations than the default at Apple Park in Cupertino, CA. The simulator will show the various provided user location options or even a custom location you specify by latitude and longitude.
Simulating Other User Locations
If you want to see the user location update in the simulator based on Apple provided presets, run the app code provided here on a simulator device. Once the app is running and you have granted permission to show your location, press the MapUserLocationButton in the upper right to zoom in. Next select Features -> Location -> Freeway Drive from the simulator menu:
If you are zoomed in close enough you will see the location update as if you were driving on the California freeway. The other location options simulate various routes and locations. You can also enter a custom location to show the user at a specific location. For example this will simulate the user being located in Times Square New York City.
If you want to simulate locations while debugging on an actual device. Debug the app from Xcode and select Debug -> Simulate Location -> then select a location
If you are zoomed in enough when selecting a location you will see the map view animate to the new location. Again, I have been unable to get locations other than the default to work in the Preview View. If you have a trick to accomplish that let me know.
All of the code and screenshots shown here were generated on Xcode 15.0.1, simulators and preview running iOS 17.0.1 and my actual iPhone running iOS 17.1.
An earlier version of this post stated that MapKit support was compatible back to iOS 14. That is not correct. While the base Map view is supported that far back the newer MapContentBuilder features that enable Markers, Annotation, etc. require iOS 17.
Here is a text code listing for a ContentView.swift that shows all the things discussed here.
//
// ContentView.swift
// Location
//
// Created by Patrick McConnell on 10/27/23.
//
import SwiftUI
import MapKit
import CoreLocation
struct ContentView: View {
@State private var lm = CLLocationManager()
var body: some View {
Map() {
UserAnnotation()
}
.mapControls {
MapUserLocationButton()
}
.onAppear {
if lm.authorizationStatus == .notDetermined {
lm.requestWhenInUseAuthorization()
}
}
}
}
#Preview {
ContentView()
}