Reputation: 13043
I want to add a circle with a certain radius in Map
with SwiftUI
only. I want to be able to show a circular overlay in the Map
with e.g. 1 km. This is a picture what I want to achieve:
The problem with the code below, is that the circle has a fixed size on the map:
import SwiftUI
import MapKit
struct ContentView: View {
static let usersLocation = CLLocationCoordinate2D(latitude: 52.0929779694589, longitude: 5.084964426384347)
@State private var region = MKCoordinateRegion(
center: ContentView.usersLocation,
span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
)
let annotations = [CurrentUsersAnnotation()]
var body: some View {
Map(
coordinateRegion: $region, annotationItems: annotations) { _ in
MapAnnotation(coordinate: ContentView.usersLocation) {
Circle()
.strokeBorder(Color.red, lineWidth: 4)
.frame(width: 40, height: 40)
}
}
}
}
struct CurrentUsersAnnotation: Identifiable {
let id = UUID() // Always unique on map
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Is there a SwiftUI-only fix? I am not looking for UIKit answers, I want to do it fully in SwiftUI.
Upvotes: 1
Views: 2645
Reputation: 441
In iOS 17, this is now supported. Apple has a more in-depth tutorial on their website https://developer.apple.com/videos/play/wwdc2023/10043/. An example from the video:
Map {
MapCircle(center: someCLLocationCoordinate2D, radius: 100)
.foregroundStyle(.cyan.opacity(0.5))
.mapOverlayLevel(level: .aboveRoads)
}
Upvotes: 1
Reputation: 29271
Overlays aren't supported but you can easily size your View
/Circle
by figuring out the ratio between the View
and the span
First, since you want kilometers you can calculate the distance between the upper and lower edge (latitude span) of the region.
extension MKCoordinateRegion{
///Identify the length of the span in meters north to south
var spanLatitude: Measurement<UnitLength>{
let loc1 = CLLocation(latitude: center.latitude - span.latitudeDelta * 0.5, longitude: center.longitude)
let loc2 = CLLocation(latitude: center.latitude + span.latitudeDelta * 0.5, longitude: center.longitude)
let metersInLatitude = loc1.distance(from: loc2)
return Measurement(value: metersInLatitude, unit: UnitLength.meters)
}
}
using GeometryReader
you can get the height
of the View
/Map
, then the ratio. Once you have the size per meter just multiply to get kilometer.
//Size per meter
let ratio = (geo.size.height/region.spanLatitude.value)
//Size per kilometer
let kilometerSize = ratio * 1000
Then use the kilometerSize
in the frame
.
It is likely not be a perfect kilometer given there are likely inconsistencies between the View
and the Map
but it is probably pretty close. If you can find something that is a known kilometer you can test it out.
struct ScaledAnnotationView: View {
static let usersLocation = CLLocationCoordinate2D(latitude: 52.0929779694589, longitude: 5.084964426384347)
@State private var region = MKCoordinateRegion(
center: ScaledAnnotationView.usersLocation,latitudinalMeters: 1000, longitudinalMeters: 1000
)
let annotations = [CurrentUsersAnnotation()]
var body: some View {
//Get the size of the frame for scale
GeometryReader{ geo in
Map(
coordinateRegion: $region, annotationItems: annotations) { _ in
MapAnnotation(coordinate: ScaledAnnotationView.usersLocation) {
//Size per kilometer or any unit, just change the converted unit.
let kilometerSize = (geo.size.height/region.spanLatitude.converted(to: .kilometers).value)
Circle()
.fill(Color.red.opacity(0.5))
//Keep it a circle
.frame(width: kilometerSize, height: kilometerSize)
}
}
}
}
}
Here is Google maps distance measurement for confirmation of measurement
Upvotes: 4