I am using MapKit + iOS 17 features to display a map with the user's location. I would like to draw a 30-metre radius around the UserLocation. How can I do this? I've seen some solutions from 12 years ago but I assume there is a better way to do this now.
struct Home: View {
@State private var position: MapCameraPosition = .userLocation(fallback: .automatic)
@State private var visibleRegion: MKCoordinateRegion?
@State private var searchResults: [MKMapItem] = []
@State private var selectedResult: MKMapItem?
@State private var showDetails: Bool = false
@State private var lookAroundScene: MKLookAroundScene?
let locationManager = CLLocationManager()
var body: some View {
Map(position: $position, selection: $selectedResult) {
let customLocations: [MKMapItem] = [
createMapItem(name: "Via Palamos", coordinate: .viaPalamos),
createMapItem(name: "Larry Way", coordinate: .larryWay),
]
ForEach(customLocations, id: \.self) { result in
Marker(result.name ?? "Unknown Location", systemImage: "binoculars.fill", coordinate: result.placemark.coordinate)
.tint(.blue)
}
.annotationTitles(.hidden)
UserAnnotation()
}
.mapStyle(.standard(pointsOfInterest: .excludingAll))
.mapStyle(.standard(elevation: .realistic))
.sheet(isPresented: $showDetails, onDismiss: {
withAnimation(.snappy) {
}
}, content: {
MapDetails()
.presentationDetents([.height(750)])
.presentationBackgroundInteraction(.enabled(upThrough: .height(750)))
.presentationCornerRadius(25)
.interactiveDismissDisabled(true)
})
.onAppear {
locationManager.requestWhenInUseAuthorization()
}
//MARK: This could help animate it nicely, i think safe inset view is causing interference atm though
// .animation(.easeIn)
.onChange(of: searchResults) {
position = .automatic
}
.onChange(of: selectedResult) { oldValue, newValue in
/// Displaying Details about the Selected Place
showDetails = newValue != nil
/// Fetching Look Around Preview, when ever selection Changes
fetchLookAroundPreview()
}
.onMapCameraChange { context in
visibleRegion = context.region
}
.mapControls {
VStack {
MapUserLocationButton()
//MARK: Can I make it pitch more by defualt? and make it the automaitic mode on initilisation
MapPitchToggle()
MapCompass()
MapScaleView()
}
.buttonBorderShape(.circle)
}
}
}
How can I calculate the 30-metre radius around the user's location and display that on the map? It would need to update itself as the user's location changes too. So when there user is moving around, the 30-metre radius circle moves with them.
A simple solution would be to have a MapCircle
that changes its location according to the location updates from a CLLocationManager
.
Here is a simple example that shows a semi-transparent red circle around the user's location.
@Observable
class LocationTracker: NSObject, CLLocationManagerDelegate {
var location: CLLocationCoordinate2D? = nil
let manager = CLLocationManager()
override init() {
super.init()
manager.delegate = self
manager.requestWhenInUseAuthorization()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
location = locations.first?.coordinate
}
deinit {
manager.stopUpdatingLocation()
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
print("Changed", manager.authorizationStatus.rawValue)
if manager.authorizationStatus == .authorizedWhenInUse {
manager.startUpdatingLocation()
}
}
}
struct ContentView: View {
@State var manager: LocationTracker?
var body: some View {
Map {
UserAnnotation.init { loc in
Circle()
}
if let c = manager?.location {
MapCircle(center: c, radius: 30)
.foregroundStyle(.red.opacity(0.5))
}
}
.onAppear {
manager = LocationTracker()
}
}
}
That said, the circle only updates once or twice a second, because there is a limit to how often didUpdateLocations
is called.